use std::time::Duration;
use jsonwebtoken::Algorithm;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use crate::config::primitives::{file_path::FilePath, http_header::HttpHeaderName};
#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone)]
#[serde(deny_unknown_fields)]
pub struct JwtAuthConfig {
#[serde(default = "default_enabled")]
pub enabled: bool,
pub jwks_providers: Vec<JwksProviderSourceConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub issuers: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub audiences: Option<Vec<String>>,
#[serde(
default = "default_lookup_location",
skip_serializing_if = "Vec::is_empty"
)]
pub lookup_locations: Vec<JwtAuthPluginLookupLocation>,
#[serde(skip_serializing_if = "Option::is_none")]
pub require_authentication: Option<bool>,
#[serde(
skip_serializing_if = "Option::is_none",
default = "default_allowed_algorithms"
)]
#[schemars(with = "Option<Vec<String>>")]
pub allowed_algorithms: Option<Vec<Algorithm>>,
#[serde(default = "default_forward_claims_to_upstream_extensions")]
pub forward_claims_to_upstream_extensions: JwtClaimsForwardingConfig,
#[serde(skip_serializing_if = "Option::is_none")]
pub scopes_claim: Option<String>,
}
impl JwtAuthConfig {
pub fn is_jwt_extensions_forwarding_enabled(&self) -> bool {
self.is_jwt_auth_enabled() && self.forward_claims_to_upstream_extensions.enabled
}
pub fn is_jwt_auth_enabled(&self) -> bool {
self.enabled
}
pub fn is_jwt_auth_disabled(&self) -> bool {
!self.is_jwt_auth_enabled()
}
}
fn default_enabled() -> bool {
false
}
impl Default for JwtAuthConfig {
fn default() -> Self {
JwtAuthConfig {
enabled: default_enabled(),
require_authentication: None,
lookup_locations: vec![],
jwks_providers: vec![],
forward_claims_to_upstream_extensions: default_forward_claims_to_upstream_extensions(),
audiences: None,
issuers: None,
allowed_algorithms: None,
scopes_claim: None,
}
}
}
#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone)]
pub struct JwtClaimsForwardingConfig {
pub enabled: bool,
pub field_name: String,
}
fn default_forward_claims_to_upstream_extensions() -> JwtClaimsForwardingConfig {
JwtClaimsForwardingConfig {
enabled: false,
field_name: "jwt".to_string(),
}
}
#[derive(Deserialize, Serialize, Debug, Clone, JsonSchema)]
#[serde(tag = "source")]
pub enum JwksProviderSourceConfig {
#[serde(rename = "file")]
#[schemars(title = "file")]
File {
#[serde(rename = "path")]
file: FilePath,
},
#[serde(rename = "remote")]
#[schemars(title = "remote")]
Remote {
url: String,
#[serde(
deserialize_with = "humantime_serde::deserialize",
serialize_with = "humantime_serde::serialize",
default = "default_polling_interval"
)]
#[schemars(with = "String")]
polling_interval: Option<Duration>,
prefetch: Option<bool>,
},
}
fn default_polling_interval() -> Option<Duration> {
Some(Duration::from_secs(10 * 60))
}
pub fn default_lookup_location() -> Vec<JwtAuthPluginLookupLocation> {
vec![JwtAuthPluginLookupLocation::Header {
name: "Authorization".into(),
prefix: Some("Bearer".to_string()),
}]
}
pub fn default_allowed_algorithms() -> Option<Vec<Algorithm>> {
Some(vec![
Algorithm::HS256,
Algorithm::HS384,
Algorithm::HS512,
Algorithm::RS256,
Algorithm::RS384,
Algorithm::RS512,
Algorithm::ES256,
Algorithm::ES384,
Algorithm::PS256,
Algorithm::PS384,
Algorithm::PS512,
Algorithm::EdDSA,
])
}
#[derive(Deserialize, Serialize, Debug, Clone, JsonSchema)]
#[serde(tag = "source")]
pub enum JwtAuthPluginLookupLocation {
#[serde(rename = "header")]
#[schemars(title = "header")]
Header {
name: HttpHeaderName,
prefix: Option<String>,
},
#[serde(rename = "cookies")]
#[schemars(title = "cookies")]
Cookie { name: String },
}
#[cfg(test)]
mod tests {
use super::JwtAuthConfig;
#[test]
fn scopes_claim_defaults_to_none() {
let config = JwtAuthConfig::default();
assert_eq!(config.scopes_claim, None);
}
#[test]
fn scopes_claim_is_none_when_not_specified_in_config() {
let config = serde_json::from_str::<JwtAuthConfig>(r#"{"jwks_providers":[]}"#)
.expect("config should parse");
assert_eq!(config.scopes_claim, None);
}
#[test]
fn scopes_claim_parses_configured_claim_name() {
let config = serde_json::from_str::<JwtAuthConfig>(
r#"{"jwks_providers":[],"scopes_claim":"roles"}"#,
)
.expect("config should parse");
assert_eq!(config.scopes_claim, Some("roles".to_string()));
}
}