use std::path::Path;
use std::sync::Arc;
use tonic::metadata::MetadataMap;
use tonic::service::Interceptor;
use tonic::{Request, Status};
pub trait Authenticator: Send + Sync + 'static {
fn authenticate(&self, bearer: Option<&str>) -> bool;
}
pub struct StaticToken(Option<String>);
impl StaticToken {
#[must_use]
pub const fn new(token: Option<String>) -> Self {
Self(token)
}
}
impl Authenticator for StaticToken {
fn authenticate(&self, bearer: Option<&str>) -> bool {
match &self.0 {
None => true,
Some(expected) => bearer == Some(expected.as_str()),
}
}
}
fn bearer(metadata: &MetadataMap) -> Option<&str> {
metadata
.get("authorization")
.and_then(|value| value.to_str().ok())
.and_then(|value| value.strip_prefix("Bearer "))
}
#[derive(Clone)]
pub struct AuthInterceptor(Arc<dyn Authenticator>);
impl AuthInterceptor {
#[must_use]
pub fn new(authenticator: Arc<dyn Authenticator>) -> Self {
Self(authenticator)
}
}
impl Interceptor for AuthInterceptor {
fn call(&mut self, request: Request<()>) -> Result<Request<()>, Status> {
if self.0.authenticate(bearer(request.metadata())) {
Ok(request)
} else {
Err(Status::unauthenticated("invalid or missing bearer token"))
}
}
}
#[derive(Clone, Default)]
pub struct TokenInterceptor(Option<String>);
impl TokenInterceptor {
#[must_use]
pub const fn new(token: Option<String>) -> Self {
Self(token)
}
}
impl Interceptor for TokenInterceptor {
fn call(&mut self, mut request: Request<()>) -> Result<Request<()>, Status> {
if let Some(token) = &self.0 {
let value = format!("Bearer {token}")
.parse()
.map_err(|_| Status::internal("token is not valid metadata"))?;
request.metadata_mut().insert("authorization", value);
}
Ok(request)
}
}
pub fn server_tls(
cert_pem: &Path,
key_pem: &Path,
) -> Result<tonic::transport::ServerTlsConfig, std::io::Error> {
let cert = std::fs::read(cert_pem)?;
let key = std::fs::read(key_pem)?;
Ok(tonic::transport::ServerTlsConfig::new()
.identity(tonic::transport::Identity::from_pem(cert, key)))
}
pub fn client_tls(
ca_pem: Option<&Path>,
domain: Option<&str>,
) -> Result<tonic::transport::ClientTlsConfig, std::io::Error> {
let mut config = tonic::transport::ClientTlsConfig::new().with_enabled_roots();
if let Some(path) = ca_pem {
let ca = std::fs::read(path)?;
config = config.ca_certificate(tonic::transport::Certificate::from_pem(ca));
}
if let Some(domain) = domain {
config = config.domain_name(domain);
}
Ok(config)
}