Crate tarcza

Crate tarcza 

Source
Expand description

Tarcza’s an authentication toolkit, which is meant to help you integrate authentication easier, no matter what stack you are using or what are your needs.

It’s not a fully fledged “Authentication Framework”, rather a toolkit to simply integrate authentication yourself.

Tarcza offers 3 main functionalities:

  • Storing user details in backend of your choosing
  • Authenticating user from their ID and password
  • Handling user sessions with JWT

Everything happen via StoreHandler trait and Authenticator struct, which seaminglesly handle: password hashing (using Argon2), JWT allocation, storing user info.

API is meant to be simple and flexible, giving you full freedom to implement:

  • How is user info (ID and password hash) stored in the backend (via StoreHandler trait)
  • How is user represented internally (again, via StoreHandler which can return any User type you want, after successful authentication)
  • How strong is hashing (via Argon2Preset enum, which includes fine defaults, but you can also define your own parameters if you wish)
  • JWT handling (JwtHandler struct, like Argon2Preset, includes default options for quickly creating JWT handlers, while also giving you option to select any Algorithm you want)

Tarcza is meant to be framework/storage/stack agnostic, letting you use whatever you want, while handling authentication needs for you

§Example usage:

use std::{
    collections::HashMap,
    sync::{Arc, Mutex},
    time::Duration
};

use tarcza;

// In real code you would probably use an actual Database/Storage, but you can also do this I'm not your mom
// In this example we just keep HashMap storing user ID and password hash
struct ExampleDB {
    users: Arc<Mutex<HashMap<String, String>>>
}

impl ExampleDB {
    fn new() -> Self {
        let db = Arc::new(Mutex::new(HashMap::new()));
        ExampleDB { users: db }
    }
}

// Internal representation of authenticating User
// In this example it's just gonna keep it's own ID here;
type User = String;

#[async_trait::async_trait]
impl tarcza::StoreHandler<User> for ExampleDB {
    async fn store_user(&self, id: &str, password_hash: &str) -> Result<(), tarcza::AuthenterError> {
        self.users.lock().map_err(|e| tarcza::AuthenterError::BackendError(e.to_string().into()))?.insert(id.to_string(), password_hash.to_string());
        Ok(())
    }
    async fn get_password_hash(&self, id: &str) -> Result<String, tarcza::AuthenterError> {
        self.users.lock().map_err(|e| tarcza::AuthenterError::BackendError(e.to_string().into()))?.get(id).cloned().ok_or(tarcza::AuthenterError::NoUser)
    }
    async fn get_user(&self, id: &str) -> Result<User, tarcza::AuthenterError> {
        Ok(id.to_string())
    }
}

#[tokio::main]
async fn main() {
    let db = Arc::new(ExampleDB::new());
    // Here you declare JwtHandler which is used to allocate tokens
    let jwt = tarcza::JwtHandler::new_hmac("super unique secure".as_bytes());
    // `tarcza::Argon2Preset` is an Enum containing either: one of the default presets, or your own Argon2 instance if you want to set your own parameters
    // `Duration` here declares for how long are JWT tokens valid
    let auth = tarcza::Authenticator::new(jwt, tarcza::Argon2Preset::Low, db, Duration::from_secs(60));

    let user_id = "user";
    // Tarcza requires to pass user password as `SecretString` (from `Secrecy` crate, re-imported for convenience)
    // It enforces that plaintext password is properly cleaned after usage (unless you copy it before wrapping, you can do it even tho I wouldn't recommend)
    let password: tarcza::SecretString = "password".into();

    // Let's register an user!
    assert!(auth.register_user(&user_id, password).await.is_ok());
    // Since `auth.register_user` returned Ok, it means that user is now registered, let's try to log in.
    // Our password has been consumed, so we have to declare a new one
    let password: tarcza::SecretString = "password".into();
     
    let token = auth.login(&user_id, password).await;
    assert!(token.is_ok());
    // Login is also Ok! It means that we are logged in, and `Authenticator` returned a JWT which we can now use to get our `User`!
    assert!(auth.login_jwt(&token.unwrap()).await.is_ok_and(|v| v == user_id));
    // Since in our example our `User` is just a String containing user_id, we can see that we are successfuly authenticated!
}

This example shows basic workflow of registering user, receiving JWT token for authentication, and using said JWT to get our internal representation of User type.

Re-exports§

pub use argon2presets::Argon2Preset;
pub use jwt::JwtHandler;

Modules§

argon2presets
jwt

Structs§

Authenticator
Handles authorization pipeline

Enums§

AuthenterError
Represents errors that can be caused by backend
AuthenticatorError

Traits§

StoreHandler
Trait over backend for persisting user data

Type Aliases§

SecretString
Secret string type.