nythos-core 0.1.2

Infrastructure-free Rust core library for Nythos authentication and authorization.
Documentation
use nythos_core::{
    AccessToken, AuthError, Claims, NythosResult, Password, PasswordHash, PasswordHasher,
    RevocationChecker, SessionId, TenantId, TokenSigner, UserId,
};
use std::{cell::RefCell, collections::BTreeSet, str::FromStr};

use crate::support::fixtures::{canonical_access_token_ttl, canonical_issued_at};

#[derive(Default)]
pub struct FakePasswordHasher;

impl PasswordHasher for FakePasswordHasher {
    async fn hash(&self, password: &Password) -> NythosResult<PasswordHash> {
        PasswordHash::new(format!("argon2id${}", password.as_str()))
    }

    async fn verify(&self, password: &Password, hash: &PasswordHash) -> NythosResult<bool> {
        Ok(hash.as_str() == format!("argon2id${}", password.as_str()))
    }
}

#[derive(Default)]
pub struct FakeTokenSigner;

impl TokenSigner for FakeTokenSigner {
    async fn sign(&self, claims: &Claims) -> NythosResult<AccessToken> {
        AccessToken::new(format!(
            "signed:{}:{}",
            claims.subject(),
            claims.tenant_id()
        ))
    }

    async fn verify(&self, token: &AccessToken) -> NythosResult<Claims> {
        if token.as_str().is_empty() {
            return Err(AuthError::InvalidCredentials);
        }

        let mut parts = token.as_str().splitn(3, ':');
        let Some("signed") = parts.next() else {
            return Err(AuthError::InvalidCredentials);
        };
        let Some(encoded_subject) = parts.next() else {
            return Err(AuthError::InvalidCredentials);
        };
        let Some(encoded_tenant) = parts.next() else {
            return Err(AuthError::InvalidCredentials);
        };

        let user_id =
            UserId::from_str(encoded_subject).map_err(|_| AuthError::InvalidCredentials)?;
        let tenant_id =
            TenantId::from_str(encoded_tenant).map_err(|_| AuthError::InvalidCredentials)?;

        Claims::access(
            user_id,
            tenant_id,
            canonical_issued_at(),
            canonical_access_token_ttl(),
        )
    }
}

#[derive(Default)]
pub struct FakeRevocationChecker {
    revoked: RefCell<BTreeSet<SessionId>>,
}

impl FakeRevocationChecker {
    pub fn mark_revoked(&self, session_id: SessionId) {
        self.revoked.borrow_mut().insert(session_id);
    }
}

impl RevocationChecker for FakeRevocationChecker {
    async fn is_revoked(&self, session_id: SessionId) -> NythosResult<bool> {
        Ok(self.revoked.borrow().contains(&session_id))
    }
}