Skip to content

Commit

Permalink
rpc: add support for basic and bearer auth to grpc client
Browse files Browse the repository at this point in the history
  • Loading branch information
bmwill committed Jan 27, 2025
1 parent 695b712 commit 56eb94c
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 3 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/sui-rpc-api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ tower.workspace = true
tracing.workspace = true
tokio-stream.workspace = true
async-stream.workspace = true
base64.workspace = true

fastcrypto.workspace = true
sui-types.workspace = true
Expand Down
79 changes: 76 additions & 3 deletions crates/sui-rpc-api/src/client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ pub struct Client {
#[allow(unused)]
uri: http::Uri,
channel: tonic::transport::Channel,
auth: AuthInterceptor,
}

impl Client {
Expand All @@ -56,11 +57,24 @@ impl Client {
}
let channel = endpoint.connect_lazy();

Ok(Self { uri, channel })
Ok(Self {
uri,
channel,
auth: Default::default(),
})
}

pub fn with_auth(mut self, auth: AuthInterceptor) -> Self {
self.auth = auth;
self
}

pub fn raw_client(&self) -> NodeServiceClient<tonic::transport::Channel> {
NodeServiceClient::new(self.channel.clone())
pub fn raw_client(
&self,
) -> NodeServiceClient<
tonic::service::interceptor::InterceptedService<tonic::transport::Channel, AuthInterceptor>,
> {
NodeServiceClient::with_interceptor(self.channel.clone(), self.auth.clone())
}

pub async fn get_latest_checkpoint(&self) -> Result<CertifiedCheckpointSummary> {
Expand Down Expand Up @@ -393,3 +407,62 @@ fn status_from_error_with_metadata<T: Into<BoxError>>(err: T, metadata: Metadata
*status.metadata_mut() = metadata;
status
}

#[derive(Clone, Debug, Default)]
pub struct AuthInterceptor {
auth: Option<tonic::metadata::MetadataValue<tonic::metadata::Ascii>>,
}

impl AuthInterceptor {
/// Enable HTTP basic authentication with a username and optional password.
pub fn basic<U, P>(username: U, password: Option<P>) -> Self
where
U: std::fmt::Display,
P: std::fmt::Display,
{
use base64::prelude::BASE64_STANDARD;
use base64::write::EncoderWriter;
use std::io::Write;

let mut buf = b"Basic ".to_vec();
{
let mut encoder = EncoderWriter::new(&mut buf, &BASE64_STANDARD);
let _ = write!(encoder, "{username}:");
if let Some(password) = password {
let _ = write!(encoder, "{password}");
}
}
let mut header = tonic::metadata::MetadataValue::try_from(buf)
.expect("base64 is always valid HeaderValue");
header.set_sensitive(true);

Self { auth: Some(header) }
}

/// Enable HTTP bearer authentication.
pub fn bearer<T>(token: T) -> Self
where
T: std::fmt::Display,
{
let header_value = format!("Bearer {token}");
let mut header = tonic::metadata::MetadataValue::try_from(header_value)
.expect("token is always valid HeaderValue");
header.set_sensitive(true);

Self { auth: Some(header) }
}
}

impl tonic::service::Interceptor for AuthInterceptor {
fn call(
&mut self,
mut request: tonic::Request<()>,
) -> std::result::Result<tonic::Request<()>, Status> {
if let Some(auth) = self.auth.clone() {
request
.metadata_mut()
.insert(http::header::AUTHORIZATION.as_str(), auth);
}
Ok(request)
}
}

0 comments on commit 56eb94c

Please sign in to comment.