use std::collections::HashMap;
use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation};
use security_core::identity::AuthenticatedIdentity;
use security_core::severity::SecuritySeverity;
use security_events::emit::emit_security_event;
use security_events::event::{EventOutcome, SecurityEvent};
use security_events::kind::EventKind;
use time::OffsetDateTime;
use uuid::Uuid;
use crate::authenticator::{private, AuthenticationRequest};
use crate::error::IdentityError;
pub struct TokenValidatorConfig {
pub issuer: String,
pub audience: String,
pub secret: Vec<u8>,
}
pub enum AlgorithmConfig {
RS256 {
decoding_key: DecodingKey,
},
ES256 {
decoding_key: DecodingKey,
},
}
pub struct AsymmetricTokenValidatorConfig {
pub issuer: String,
pub audience: String,
pub algorithm: AlgorithmConfig,
}
pub struct AsymmetricTokenValidator {
config: AsymmetricTokenValidatorConfig,
}
impl AsymmetricTokenValidator {
#[must_use]
pub fn new(config: AsymmetricTokenValidatorConfig) -> Self {
Self { config }
}
fn validate_jwt(&self, token: &str) -> Result<JwtClaims, IdentityError> {
let (algorithm, key) = match &self.config.algorithm {
AlgorithmConfig::RS256 { decoding_key } => (Algorithm::RS256, decoding_key),
AlgorithmConfig::ES256 { decoding_key } => (Algorithm::ES256, decoding_key),
};
let mut validation = Validation::new(algorithm);
validation.set_issuer(&[&self.config.issuer]);
validation.set_audience(&[&self.config.audience]);
decode::<JwtClaims>(token, key, &validation)
.map(|data| data.claims)
.map_err(|e| {
use jsonwebtoken::errors::ErrorKind;
match e.kind() {
ErrorKind::ExpiredSignature => IdentityError::TokenExpired,
ErrorKind::InvalidIssuer | ErrorKind::InvalidAudience => {
IdentityError::InvalidCredentials
}
_ => IdentityError::TokenMalformed,
}
})
}
}
impl private::Sealed for AsymmetricTokenValidator {}
impl crate::authenticator::Authenticator for AsymmetricTokenValidator {
async fn authenticate(
&self,
request: &AuthenticationRequest,
) -> Result<AuthenticatedIdentity, IdentityError> {
let claims = self.validate_jwt(&request.token).inspect_err(|_e| {
emit_security_event(SecurityEvent::new(
EventKind::AuthnFailure,
SecuritySeverity::High,
EventOutcome::Failure,
));
})?;
let actor_uuid: Uuid = claims.sub.parse().map_err(|_| {
emit_security_event(SecurityEvent::new(
EventKind::AuthnFailure,
SecuritySeverity::High,
EventOutcome::Failure,
));
IdentityError::TokenMalformed
})?;
let tenant_id = claims
.tenant
.as_deref()
.and_then(|t| t.parse::<Uuid>().ok())
.map(security_core::types::TenantId::from);
Ok(AuthenticatedIdentity {
actor_id: security_core::types::ActorId::from(actor_uuid),
tenant_id,
roles: claims.roles,
attributes: HashMap::new(),
authenticated_at: OffsetDateTime::now_utc(),
})
}
}
impl security_core::identity::IdentitySource for AsymmetricTokenValidator {
async fn resolve(
&self,
token: &str,
) -> Result<AuthenticatedIdentity, security_core::identity::IdentityResolutionError> {
use crate::authenticator::{AuthenticationRequest, Authenticator, TokenKind};
let request = AuthenticationRequest {
token: token.to_owned(),
token_kind: TokenKind::BearerJwt,
};
self.authenticate(&request).await.map_err(|e| match e {
IdentityError::TokenExpired => {
security_core::identity::IdentityResolutionError::Expired
}
IdentityError::ProviderUnavailable => {
security_core::identity::IdentityResolutionError::ProviderUnavailable
}
_ => security_core::identity::IdentityResolutionError::InvalidToken,
})
}
}
#[derive(serde::Serialize, serde::Deserialize)]
struct JwtClaims {
sub: String,
exp: u64,
iat: u64,
iss: String,
aud: String,
#[serde(skip_serializing_if = "Option::is_none")]
tenant: Option<String>,
#[serde(default)]
roles: Vec<String>,
}
pub struct TokenValidator {
config: TokenValidatorConfig,
}
impl TokenValidator {
#[must_use]
pub fn new(config: TokenValidatorConfig) -> Self {
Self { config }
}
fn validate_jwt(&self, token: &str) -> Result<JwtClaims, IdentityError> {
let key = DecodingKey::from_secret(&self.config.secret);
let mut validation = Validation::new(Algorithm::HS256);
validation.set_issuer(&[&self.config.issuer]);
validation.set_audience(&[&self.config.audience]);
decode::<JwtClaims>(token, &key, &validation)
.map(|data| data.claims)
.map_err(|e| {
use jsonwebtoken::errors::ErrorKind;
match e.kind() {
ErrorKind::ExpiredSignature => IdentityError::TokenExpired,
ErrorKind::InvalidIssuer | ErrorKind::InvalidAudience => {
IdentityError::InvalidCredentials
}
_ => IdentityError::TokenMalformed,
}
})
}
}
impl private::Sealed for TokenValidator {}
impl crate::authenticator::Authenticator for TokenValidator {
async fn authenticate(
&self,
request: &AuthenticationRequest,
) -> Result<AuthenticatedIdentity, IdentityError> {
let claims = self.validate_jwt(&request.token).inspect_err(|_e| {
emit_security_event(SecurityEvent::new(
EventKind::AuthnFailure,
SecuritySeverity::High,
EventOutcome::Failure,
));
})?;
let actor_uuid: Uuid = claims.sub.parse().map_err(|_| {
emit_security_event(SecurityEvent::new(
EventKind::AuthnFailure,
SecuritySeverity::High,
EventOutcome::Failure,
));
IdentityError::TokenMalformed
})?;
let tenant_id = claims
.tenant
.as_deref()
.and_then(|t| t.parse::<Uuid>().ok())
.map(security_core::types::TenantId::from);
Ok(AuthenticatedIdentity {
actor_id: security_core::types::ActorId::from(actor_uuid),
tenant_id,
roles: claims.roles,
attributes: HashMap::new(),
authenticated_at: OffsetDateTime::now_utc(),
})
}
}
impl security_core::identity::IdentitySource for TokenValidator {
async fn resolve(
&self,
token: &str,
) -> Result<AuthenticatedIdentity, security_core::identity::IdentityResolutionError> {
use crate::authenticator::{AuthenticationRequest, Authenticator, TokenKind};
let request = AuthenticationRequest {
token: token.to_owned(),
token_kind: TokenKind::BearerJwt,
};
self.authenticate(&request).await.map_err(|e| match e {
IdentityError::TokenExpired => {
security_core::identity::IdentityResolutionError::Expired
}
IdentityError::ProviderUnavailable => {
security_core::identity::IdentityResolutionError::ProviderUnavailable
}
_ => security_core::identity::IdentityResolutionError::InvalidToken,
})
}
}