#![forbid(unsafe_code)]
#![warn(missing_docs)]
use access_control::AccessControlRequestBuilder;
use arc_swap::ArcSwap;
use std::{borrow::Cow, sync::Arc};
use anyhow::anyhow;
use authly_common::{
access_token::AuthlyAccessTokenClaims,
id::Eid,
proto::service::{self as proto, authly_service_client::AuthlyServiceClient},
service::PropertyMapping,
};
use http::header::COOKIE;
use token::AccessToken;
use tonic::Request;
pub use builder::ClientBuilder;
pub use error::Error;
pub mod identity;
pub mod token;
pub mod access_control;
mod builder;
mod error;
const K8S_SA_TOKENFILE: &str = "/var/run/secrets/kubernetes.io/serviceaccount/token";
#[derive(Clone)]
pub struct Client {
inner: Arc<ClientInner>,
}
struct ClientInner {
service: AuthlyServiceClient<tonic::transport::Channel>,
jwt_decoding_key: jsonwebtoken::DecodingKey,
resource_property_mapping: Arc<ArcSwap<PropertyMapping>>,
}
impl Client {
pub fn builder() -> ClientBuilder {
ClientBuilder {
authly_local_ca: None,
identity: None,
jwt_decoding_key: None,
url: Cow::Borrowed("https://authly"),
}
}
pub async fn entity_id(&self) -> Result<Eid, Error> {
let mut service = self.inner.service.clone();
let metadata = service
.get_metadata(proto::Empty::default())
.await
.map_err(error::tonic)?
.into_inner();
Eid::from_bytes(&metadata.entity_id).ok_or_else(id_codec_error)
}
pub async fn label(&self) -> Result<String, Error> {
let mut service = self.inner.service.clone();
let metadata = service
.get_metadata(proto::Empty::default())
.await
.map_err(error::tonic)?
.into_inner();
Ok(metadata.label)
}
pub fn access_control_request(&self) -> AccessControlRequestBuilder<'_> {
AccessControlRequestBuilder::new(self)
}
pub async fn get_access_token(&self, session_token: &str) -> Result<Arc<AccessToken>, Error> {
let mut service = self.inner.service.clone();
let mut request = Request::new(proto::Empty::default());
request.metadata_mut().append(
COOKIE.as_str(),
format!("session-cookie={session_token}")
.parse()
.map_err(error::unclassified)?,
);
let proto = service
.get_access_token(request)
.await
.map_err(error::tonic)?
.into_inner();
let validation = jsonwebtoken::Validation::new(jsonwebtoken::Algorithm::ES256);
let token_data = jsonwebtoken::decode::<AuthlyAccessTokenClaims>(
&proto.token,
&self.inner.jwt_decoding_key,
&validation,
)
.map_err(|err| Error::InvalidAccessToken(err.into()))?;
Ok(Arc::new(AccessToken {
token: proto.token,
claims: token_data.claims,
}))
}
}
fn id_codec_error() -> Error {
Error::Codec(anyhow!("id decocing error"))
}