about summary refs log tree commit diff
path: root/kittybox-rs/util
diff options
context:
space:
mode:
Diffstat (limited to 'kittybox-rs/util')
-rw-r--r--kittybox-rs/util/Cargo.toml26
-rw-r--r--kittybox-rs/util/src/error.rs2
-rw-r--r--kittybox-rs/util/src/lib.rs82
3 files changed, 98 insertions, 12 deletions
diff --git a/kittybox-rs/util/Cargo.toml b/kittybox-rs/util/Cargo.toml
index cdad17f..f36b6d8 100644
--- a/kittybox-rs/util/Cargo.toml
+++ b/kittybox-rs/util/Cargo.toml
@@ -5,16 +5,18 @@ edition = "2021"
 
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
-[dependencies]
-[dependencies.serde]         # A generic serialization/deserialization framework
-version = "^1.0.125"
-features = ["derive"]
-
-[dependencies.serde_json]
-version = "^1.0.64"
+[features]
+fs = ["rand", "tokio", "tokio/fs"]
 
-[dependencies.axum-core]
-version = "^0.2.6"
-
-[dependencies.http]
-version = "^0.2.7"
\ No newline at end of file
+[dependencies]
+serde = { version = "^1.0.125", features = ["derive"] }
+serde_json = "^1.0.64"
+axum-core = "^0.2.6"
+http = "^0.2.7"
+[dependencies.rand]
+version = "^0.8.5"
+optional = true
+[dependencies.tokio]
+version = "^1.16.1"
+features = ["tracing"]
+optional = true
diff --git a/kittybox-rs/util/src/error.rs b/kittybox-rs/util/src/error.rs
index 79f43ef..7edf176 100644
--- a/kittybox-rs/util/src/error.rs
+++ b/kittybox-rs/util/src/error.rs
@@ -4,6 +4,7 @@ use axum_core::response::{Response, IntoResponse};
 
 #[derive(Serialize, Deserialize, PartialEq, Debug)]
 #[serde(rename_all = "snake_case")]
+/// Kinds of errors that can happen within a Micropub operation.
 pub enum ErrorType {
     /// An erroneous attempt to create something that already exists.
     AlreadyExists,
@@ -27,6 +28,7 @@ pub enum ErrorType {
 #[derive(Serialize, Deserialize, Debug)]
 pub struct MicropubError {
     pub error: ErrorType,
+    // TODO use Cow<'static, str> to save on heap allocations
     pub error_description: String,
 }
 
diff --git a/kittybox-rs/util/src/lib.rs b/kittybox-rs/util/src/lib.rs
index debe589..f30dc2d 100644
--- a/kittybox-rs/util/src/lib.rs
+++ b/kittybox-rs/util/src/lib.rs
@@ -1,3 +1,9 @@
+#![warn(missing_docs)]
+//! Small things that couldn't fit elsewhere in Kittybox, yet may be
+//! useful on their own or in multiple Kittybox crates.
+//!
+//! Some things are gated behind features, namely:
+//!  - `fs` - enables use of filesystem-related utilities
 use serde::{Deserialize, Serialize};
 
 #[derive(Clone, Serialize, Deserialize)]
@@ -17,5 +23,81 @@ pub struct MicropubChannel {
     pub name: String,
 }
 
+/// Common errors from the IndieWeb protocols that can be reused between modules.
 pub mod error;
 pub use error::{ErrorType, MicropubError};
+
+/// Common data-types useful in creating smart authentication systems.
+pub mod auth {
+    #[derive(PartialEq, Eq, Hash, Clone, Copy)]
+    pub enum EnrolledCredential {
+        /// An indicator that a password is enrolled. Passwords can be
+        /// used to recover from a lost token.
+        Password,
+        /// An indicator that one or more WebAuthn credentials were
+        /// enrolled.
+        WebAuthn
+    }
+}
+
+#[cfg(feature = "fs")]
+/// Commonly-used operations with the file system in Kittybox's
+/// underlying storage mechanisms.
+pub mod fs {
+    use std::io::{self, Result};
+    use std::path::{Path, PathBuf};
+    use rand::{Rng, distributions::Alphanumeric};
+    use tokio::fs;
+
+    /// Create a temporary file named `temp.[a-zA-Z0-9]{length}` in
+    /// the given location and immediately open it. Returns the
+    /// filename and the corresponding file handle. It is the caller's
+    /// responsibility to clean up the temporary file when it is no
+    /// longer needed.
+    ///
+    /// Uses [`OpenOptions::create_new`][fs::OpenOptions::create_new]
+    /// to detect filename collisions, in which case it will
+    /// automatically retry until the operation succeeds.
+    ///
+    /// # Errors
+    ///
+    /// Returns the underlying [`io::Error`] if the operation fails
+    /// due to reasons other than filename collision.
+    pub async fn mktemp<T, B>(dir: T, basename: B, length: usize) -> Result<(PathBuf, fs::File)>
+    where
+        T: AsRef<Path>,
+        B: Into<Option<&'static str>>
+    {
+        let dir = dir.as_ref();
+        let basename = basename.into().unwrap_or("");
+        fs::create_dir_all(dir).await?;
+
+        loop {
+            let filename = dir.join(format!(
+                "{}{}{}",
+                basename,
+                if basename.is_empty() { "" } else { "." },
+                {
+                    let string = rand::thread_rng()
+                        .sample_iter(&Alphanumeric)
+                        .take(length)
+                        .collect::<Vec<u8>>();
+                    String::from_utf8(string).unwrap()
+                }
+            ));
+
+            match fs::OpenOptions::new()
+                .create_new(true)
+                .write(true)
+                .open(&filename)
+                .await
+            {
+                Ok(file) => return Ok((filename, file)),
+                Err(err) => match err.kind() {
+                    io::ErrorKind::AlreadyExists => continue,
+                    _ => return Err(err)
+                }
+            }
+        }
+    }
+}