use crate::error::{Error, ErrorCode};
use serde::Deserialize;
use std::fmt::Debug;
use volga::auth::{Algorithm, AuthClaims, Authorizer, BearerAuthConfig, DecodingKey, predicate};
const ERR_NO_CLAIMS: &str = "Claims are not provided";
const ERR_UNAUTHORIZED: &str = "Subject is not authorized to invoke this";
#[derive(Default, Clone, Debug, Deserialize)]
pub struct DefaultClaims {
#[serde(skip_serializing_if = "Option::is_none")]
pub sub: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub iss: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub aud: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub exp: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub nbf: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub iat: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub jti: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub role: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub roles: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub permissions: Option<Vec<String>>,
}
impl AuthClaims for DefaultClaims {
#[inline]
fn role(&self) -> Option<&str> {
self.role.as_deref()
}
#[inline]
fn roles(&self) -> Option<&[String]> {
self.roles.as_deref()
}
#[inline]
fn permissions(&self) -> Option<&[String]> {
self.permissions.as_deref()
}
}
pub struct AuthConfig<C: AuthClaims = DefaultClaims> {
inner: BearerAuthConfig,
authorizer: Authorizer<C>,
}
impl Debug for AuthConfig {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("AuthConfig { .. }")
}
}
impl Default for AuthConfig {
#[inline]
fn default() -> Self {
Self {
inner: BearerAuthConfig::default(),
authorizer: default_auth_rules(),
}
}
}
impl From<AuthConfig> for BearerAuthConfig {
#[inline]
fn from(auth: AuthConfig) -> Self {
auth.inner
}
}
impl<C: AuthClaims> AuthConfig<C> {
pub fn set_decoding_key(mut self, secret: &[u8]) -> Self {
self.inner = self
.inner
.set_decoding_key(DecodingKey::from_secret(secret));
self
}
pub fn with_alg(mut self, alg: Algorithm) -> Self {
self.inner = self.inner.with_alg(alg);
self
}
pub fn with_aud<I, T>(mut self, aud: I) -> Self
where
T: ToString,
I: AsRef<[T]>,
{
self.inner = self.inner.with_aud(aud);
self
}
pub fn with_iss<I, T>(mut self, iss: I) -> Self
where
T: ToString,
I: AsRef<[T]>,
{
self.inner = self.inner.with_iss(iss);
self
}
pub fn validate_aud(mut self, validate: bool) -> Self {
self.inner = self.inner.validate_aud(validate);
self
}
pub fn validate_exp(mut self, validate: bool) -> Self {
self.inner = self.inner.validate_exp(validate);
self
}
pub fn validate_nbf(mut self, validate: bool) -> Self {
self.inner = self.inner.validate_nbf(validate);
self
}
pub(crate) fn into_parts(self) -> (BearerAuthConfig, Authorizer<C>) {
(self.inner, self.authorizer)
}
}
#[inline]
pub(crate) fn validate_permissions<C: AuthClaims>(
claims: Option<&C>,
required: Option<&[String]>,
) -> Result<(), Error> {
required.map_or(Ok(()), |req| {
let claims = claims.ok_or_else(claims_missing)?;
contains_any(claims.permissions(), req)
.then_some(())
.ok_or_else(unauthorized)
})
}
#[inline]
pub(crate) fn validate_roles<C: AuthClaims>(
claims: Option<&C>,
required: Option<&[String]>,
) -> Result<(), Error> {
required.map_or(Ok(()), |req| {
let claims = claims.ok_or_else(claims_missing)?;
(contains(claims.role(), req) || contains_any(claims.roles(), req))
.then_some(())
.ok_or_else(unauthorized)
})
}
#[inline]
fn contains_any(have: Option<&[String]>, required: &[String]) -> bool {
have.is_some_and(|vals| vals.iter().any(|v| required.contains(v)))
}
#[inline]
fn contains(have: Option<&str>, required: &[String]) -> bool {
have.is_some_and(|val| required.iter().any(|r| r == val))
}
#[inline]
fn unauthorized() -> Error {
Error::new(ErrorCode::InvalidParams, ERR_UNAUTHORIZED)
}
#[inline]
fn claims_missing() -> Error {
Error::new(ErrorCode::InvalidParams, ERR_NO_CLAIMS)
}
#[inline]
pub(super) fn default_auth_rules() -> Authorizer<DefaultClaims> {
predicate(|_| true)
}