//! Tools for registering users & persisting arbitrary user data //! //! Many Gemini applications use some form of a login method in order to allow users to //! persist personal data, authenticate themselves, and login from multiple devices using //! multiple certificates. //! //! This module contains tools to help you build a system like this without stress. A //! typical workflow looks something like this: //! //! * Call [`Request::user()`] to retrieve information about a user //! * Direct any users without a certificate to create a certificate //! * Ask users with a certificate not yet linked to an account to create an account using //! [`NotSignedInUser::register()`] or link their certificate to an existing account //! with a password using [`NotSignedInUser::attach()`]. //! * You should now have a [`RegisteredUser`] either from registering/attaching a //! [`NotSignedInUser`] or because the user was already registered //! * Access and modify user data using [`RegisteredUser::as_mut()`], changes are //! automatically persisted to the database (on user drop). //! //! Use of this module requires the `user_management` feature to be enabled pub mod user; mod manager; #[cfg(feature = "user_management_routes")] mod routes; #[cfg(feature = "user_management_routes")] pub use routes::UserManagementRoutes; pub use manager::UserManager; pub use user::User; // Imports for docs #[allow(unused_imports)] use user::{NotSignedInUser, RegisteredUser}; #[allow(unused_imports)] use crate::types::Request; #[derive(Debug)] /// An error that occured in the user manager pub enum UserManagerError { /// Tried to set a user's username to a username that already exists /// /// Recommended handling: Explicitly catch the error, and display a custom warning to /// the user before asking them to try another username UsernameNotUnique, /// Attempted to validate the user's password, but they haven't set one yet /// /// Recommended handling: Inform the user that either their username or password was /// incorrect. PasswordNotSet, /// There was an error connecting to sled /// /// Recommended handling: Log a visible error for the sysadmin to see, and exit DatabaseError(sled::Error), /// There was an error running a database transaction /// /// Recommended handling: Same as [`UserManagerError::DatabaseError`] DatabaseTransactionError(sled::transaction::TransactionError), /// There was an error deserializing from the database /// /// This likely indicates that the database was generated with a different version of /// the software than is curretly being used, either because the UserData struct has /// changed, or because kochab itself has updated it's schema. /// /// Recommended handling: Log a visible error and exit. Recommend seeking a database /// migration script or deleting the database DeserializeError(DeserializeError), #[cfg(feature = "user_management_advanced")] /// There was an error hashing or checking the user's password. /// /// This likely indicates database corruption, and should be handled in the same way /// as a [`UserManagerError::DeserializeBincodeError`] Argon2Error(argon2::Error), } impl From for UserManagerError { fn from(error: sled::Error) -> Self { Self::DatabaseError(error) } } impl From for UserManagerError { fn from(error: sled::transaction::TransactionError) -> Self { Self::DatabaseTransactionError(error) } } impl From for UserManagerError { fn from(error: bincode::Error) -> Self { Self::DeserializeError(error.into()) } } impl From for UserManagerError { fn from(error: std::array::TryFromSliceError) -> Self { Self::DeserializeError(error.into()) } } impl From for UserManagerError { fn from(error: std::str::Utf8Error) -> Self { Self::DeserializeError(error.into()) } } impl From for UserManagerError { fn from(error: DeserializeError) -> Self { Self::DeserializeError(error) } } #[cfg(feature = "user_management_advanced")] impl From for UserManagerError { fn from(error: argon2::Error) -> Self { Self::Argon2Error(error) } } impl std::error::Error for UserManagerError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { Self::DatabaseError(e) => Some(e), Self::DatabaseTransactionError(e) => Some(e), Self::DeserializeError(e) => Some(e), #[cfg(feature = "user_management_advanced")] Self::Argon2Error(e) => Some(e), _ => None } } } impl std::fmt::Display for UserManagerError { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::result::Result<(), std::fmt::Error> { match self { Self::UsernameNotUnique => write!(f, "Attempted to create a user using a username that's already been taken"), Self::PasswordNotSet => write!(f, "Attempted to check the password of a user who has not set one yet"), Self::DatabaseError(e) => write!(f, "Error accessing the user database: {}", e), Self::DatabaseTransactionError(e) => write!(f, "Error accessing the user database: {}", e), Self::DeserializeError(e) => write!(f, "Recieved messy data from database, possible corruption: {}", e), #[cfg(feature = "user_management_advanced")] Self::Argon2Error(e) => write!(f, "Argon2 Error, likely malformed password hash, possible database corruption: {}", e), } } } #[derive(Debug)] /// Indicates an error deserializing from the database /// /// This likely indicates that the database was generated with /// a different version of the software than is curretly being used, either because /// the UserData struct has changed, or because kochab itself has updated it's schema. pub enum DeserializeError { /// There was an error deserializing an entire struct /// /// Most likely indicates that the struct itself changed, i.e. between versions. StructError(bincode::Error), /// The was an error deserializing a userid /// /// Likely because too many bytes were received. If you are getting this, you are /// likely using a pre-0.1.0 version of kochab, and should update to the latest /// version, or you're using a database that was created by a version of kochab that /// was released after the one you're currently using. However, as of right now, no /// such release exists or is planned. UidError(std::array::TryFromSliceError), /// There was an error deserializing a username /// /// Indicates data corruption or a misaligned database version UsernameError(std::str::Utf8Error), /// A certificate or username was linked to a non-existant user id /// /// This error should not occur, except in very unlikely scenarios, or if there's a /// bug with kochab InvalidReference(u64), } impl From for DeserializeError { fn from(error: bincode::Error) -> Self { Self::StructError(error) } } impl From for DeserializeError { fn from(error: std::array::TryFromSliceError) -> Self { Self::UidError(error) } } impl From for DeserializeError { fn from(error: std::str::Utf8Error) -> Self { Self::UsernameError(error) } } impl std::error::Error for DeserializeError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { Self::StructError(e) => Some(e), Self::UidError(e) => Some(e), Self::UsernameError(e) => Some(e), Self::InvalidReference(_) => None, } } } impl std::fmt::Display for DeserializeError { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::result::Result<(), std::fmt::Error> { match self { Self::StructError(e) => write!(f, "Failed to deserialize struct: {}", e), Self::UidError(e) => write!(f, "Got wrong number of bytes while deserializing user ID: {}", e), Self::UsernameError(e) => write!(f, "Got invalid UTF-8 instead of username: {}", e), Self::InvalidReference(uid) => write!(f, "Database refers to nonexistant user with userid {}", uid), } } } /// A result type returned by many methods within the [`user_management`] module pub type Result = std::result::Result;