reductstore 1.19.8

ReductStore is a time series database designed specifically for storing and managing large amounts of blob data.
Documentation
// Copyright 2021-2026 ReductSoftware UG
// Licensed under the Apache License, Version 2.0

use crate::auth::policy::Policy;
use crate::auth::token_repository::ManageTokens;
use reduct_base::error::ReductError;
use std::net::IpAddr;

/// Authorization by token
pub(crate) struct TokenAuthorization {
    api_token: String,
}

impl TokenAuthorization {
    /// Create a new TokenAuthorization
    pub fn new(api_token: &str) -> Self {
        Self {
            api_token: api_token.to_string(),
        }
    }

    /// Check if the request is authorized.
    pub async fn check<Plc>(
        &self,
        authorization_header: Option<&str>,
        client_ip: Option<IpAddr>,
        repo: &mut (dyn ManageTokens + Send),
        policy: Plc,
    ) -> Result<(), ReductError>
    where
        Plc: Policy,
    {
        if self.api_token.is_empty() {
            return Ok(());
        }

        let token = repo.validate_token(authorization_header, client_ip).await;
        policy.validate(token)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::auth::policy::{AnonymousPolicy, FullAccessPolicy};
    use crate::auth::token_repository::BoxedTokenRepository;
    use crate::auth::token_repository::TokenRepositoryBuilder;
    use crate::cfg::Cfg;
    use tempfile::tempdir;

    #[tokio::test]
    async fn test_anonymous_policy() {
        let (mut repo, auth) = setup().await;
        let result = auth
            .check(Some("invalid"), None, repo.as_mut(), AnonymousPolicy {})
            .await;

        assert!(result.is_ok());

        let result = auth
            .check(
                Some("Bearer invalid"),
                None,
                repo.as_mut(),
                AnonymousPolicy {},
            )
            .await;

        assert!(result.is_ok());

        let result = auth
            .check(Some("Bearer test"), None, repo.as_mut(), AnonymousPolicy {})
            .await;
        assert!(result.is_ok());
    }

    #[tokio::test]
    async fn test_full_access_policy() {
        let (mut repo, auth) = setup().await;
        let result = auth
            .check(Some("invalid"), None, repo.as_mut(), FullAccessPolicy {})
            .await;

        assert_eq!(
            result,
            Err(ReductError::unauthorized(
                "No bearer token in request header"
            ))
        );

        let result = auth
            .check(
                Some("Bearer invalid"),
                None,
                repo.as_mut(),
                FullAccessPolicy {},
            )
            .await;
        assert_eq!(result, Err(ReductError::unauthorized("Invalid token")));

        let result = auth
            .check(
                Some("Bearer test"),
                None,
                repo.as_mut(),
                FullAccessPolicy {},
            )
            .await;
        assert!(result.is_ok());
    }

    async fn setup() -> (BoxedTokenRepository, TokenAuthorization) {
        let cfg = Cfg {
            api_token: "test".to_string(),
            ..Cfg::default()
        };
        let repo = TokenRepositoryBuilder::new(cfg)
            .build(tempdir().unwrap().keep())
            .await;
        let auth = TokenAuthorization::new("test");

        (repo, auth)
    }
}