Repository: influxdata/influxdb
File: influxdb3_server/src/http.rs (Rust)

Code before:

    let token = if auth_scheme == "Basic" {
        token_part_as_bytes(token)?
    } else {
        token.as_bytes().to_vec()
    };

    Ok(token)
}

fn token_part_as_bytes(token: &str) -> Result<Vec<u8>, AuthenticationError> {
    let decoded = B64_STANDARD.decode(token).map_err(|err| {
        error!(?err, "cannot decode basic auth token");
        AuthenticationError::MalformedRequest
    })?;

    let token_parts = String::from_utf8(decoded)
        .map_err(|err| {
            error!(?err, "cannot decode basic auth token to string");
            AuthenticationError::MalformedRequest
        })?
        .split(":")
        .map(|part| part.to_owned())
        .collect::<Vec<String>>();

    let token_part = token_parts.get(1).ok_or_else(|| {
        error!("cannot find token part in decoded basic auth token");
        AuthenticationError::MalformedRequest
    })?;

    Ok(token_part.as_bytes().to_vec())
}

impl From<authz::Error> for AuthenticationError {
    fn from(auth_error: authz::Error) -> Self {
        match auth_error {
            authz::Error::Forbidden => Self::Forbidden,
            _ => Self::Unauthenticated,
        }
    }
}

/// Validate a database name
///
/// A valid name:
/// - Starts with a letter or a number
/// - Is ASCII not UTF-8
/// - Contains only letters, numbers, underscores or hyphens
/// - if `accept_rp` is true, then a single slash ('/') is allowed, separating the
///   the database name from the retention policy name, e.g., '<db_name>/<rp_name>'
fn validate_db_name(name: &str, accept_rp: bool) -> Result<(), ValidateDbNameError> {

Review comment (bug):
This is a bit of a nitpick but this will not work right if the user happens to specify `:` in the username (eg, `foo:bar:apiv3_...`). Granted, username is ignored and a malformed username like this will cause the token to be `bar` which will result in a 401 so this is ok from a security POV (failing closed), but it seems we should a) ensure there is at least 1 `:`, b) split on `:` and c) take the last part, not `1`. Perhaps use `.last()` instead of `.get(1)` (and be sure the length of the vector is 2 or more)?

While I don't foresee us ever considering the username, if we ever did want to, adjusting this function would avoid a latent bug.

Code after:
        return Err(AuthenticationError::MalformedRequest);
    }

    let token = if auth_scheme == "Basic" {
        token_part_as_bytes(token)?
    } else {
        token.as_bytes().to_vec()
    };

    Ok(token)
}

fn token_part_as_bytes(token: &str) -> Result<Vec<u8>, AuthenticationError> {
    let decoded = B64_STANDARD.decode(token).map_err(|err| {
        error!(?err, "cannot decode basic auth token");
        AuthenticationError::MalformedRequest
    })?;

    let token_parts_string = String::from_utf8(decoded).map_err(|err| {
        error!(?err, "cannot decode basic auth token to string");
        AuthenticationError::MalformedRequest
    })?;

    let token_parts = token_parts_string.rsplit_once(":");
    let (username, token_part) = token_parts.ok_or_else(|| {
        error!("cannot find token part in decoded basic auth token");
        AuthenticationError::MalformedRequest
    })?;

    if username.contains(":") {
        error!("token username/password contains ':' which is not allowed");
        return Err(AuthenticationError::MalformedRequest);
    }

    Ok(token_part.as_bytes().to_vec())
}

impl From<authz::Error> for AuthenticationError {
    fn from(auth_error: authz::Error) -> Self {
        match auth_error {
            authz::Error::Forbidden => Self::Forbidden,
            _ => Self::Unauthenticated,
        }
    }
}

/// Validate a database name
///
/// A valid name:
/// - Starts with a letter or a number
/// - Is ASCII not UTF-8