Repository: libsql/libsql
File: libsql-server/src/auth/user_auth_strategies/http_basic.rs (Rust)

Code before:
use crate::auth::{constants::GRPC_AUTH_HEADER, AuthError, Authenticated};

use super::{UserAuthContext, UserAuthStrategy};

pub struct HttpBasic {
    credential: String,
}

impl UserAuthStrategy for HttpBasic {
    fn authenticate(&self, ctx: UserAuthContext) -> Result<Authenticated, AuthError> {
        tracing::trace!("executing http basic auth");
        let auth_str = ctx
            .custom_fields
            .get("authorization")
            .or_else(|| ctx.custom_fields.get("x-authorization"));

        let (_, token) = auth_str
            .ok_or(AuthError::AuthHeaderNotFound)
            .map(|s| s.split_once(' ').ok_or(AuthError::AuthStringMalformed))
            .and_then(|o| o)?;

        // NOTE: this naive comparison may leak information about the `expected_value`
        // using a timing attack
        let expected_value = self.credential.trim_end_matches('=');
        let creds_match = token.contains(expected_value);
        if creds_match {
            return Ok(Authenticated::FullAccess);
        }
        Err(AuthError::BasicRejected)
    }

    fn required_fields(&self) -> Vec<String> {
        vec!["authorization".to_string(), GRPC_AUTH_HEADER.to_string()]
    }
}

impl HttpBasic {
    pub fn new(credential: String) -> Self {
        Self { credential }

Review comment (refactor):
should we use the constant from the `http` crate here instead of hard coding this value? If the hashmap needs all lower case we could wrap the hashmap to always call `to_lower_case` to ensurse that we always hash the correct key.

We use this hard coded value in a lot of places it would be nice if we could dry that up. I feel somewhat uneasy about having a dynamic hashmap that has hard coded values in diff spots all over the code base, I think we should make this a bit more type safe since this code is pretty critical and called an almost every request.

Code after:
use crate::auth::{
    constants::{AUTH_HEADER, GRPC_AUTH_HEADER},
    AuthError, Authenticated,
};

use super::{UserAuthContext, UserAuthStrategy};

pub struct HttpBasic {
    credential: String,
}

impl UserAuthStrategy for HttpBasic {
    fn authenticate(&self, ctx: UserAuthContext) -> Result<Authenticated, AuthError> {
        tracing::trace!("executing http basic auth");
        let auth_str = ctx
            .get_field(AUTH_HEADER)
            .or_else(|| ctx.get_field(GRPC_AUTH_HEADER));

        let (_, token) = auth_str
            .ok_or(AuthError::AuthHeaderNotFound)
            .map(|s| s.split_once(' ').ok_or(AuthError::AuthStringMalformed))
            .and_then(|o| o)?;

        // NOTE: this naive comparison may leak information about the `expected_value`
        // using a timing attack
        let expected_value = self.credential.trim_end_matches('=');
        let creds_match = token.contains(expected_value);
        if creds_match {
            return Ok(Authenticated::FullAccess);
        }
        Err(AuthError::BasicRejected)
    }

    fn required_fields(&self) -> Vec<&'static str> {
        vec![AUTH_HEADER, GRPC_AUTH_HEADER]
    }
}

impl HttpBasic {