use crate::app_config;
use crate::error::cli::CliError;
use crab_vault_auth::{HttpMethod, Jwt, Permission};
use chrono::Duration;
use clap::error::ErrorKind;
use clap::{Args, Subcommand};
use std::io::{self, Read};
#[derive(Args)]
pub struct JwtCommandAndArgs {
#[command(subcommand)]
pub command: Command,
}
#[derive(Subcommand, Clone)]
pub enum Command {
#[command(name = "generate")]
Generate(GenerateArgs),
#[command(name = "verify")]
Verify,
}
#[derive(Args, Clone)]
pub struct GenerateArgs {
#[arg(long, default_value_t = 0)]
pub nbf_offset: i64,
#[arg(long, default_value_t = 3600)]
pub exp_offset: i64,
#[arg(long)]
pub issue_as: Option<String>,
#[arg(long, value_delimiter = ',')]
pub audiences: Option<Vec<String>>,
#[arg(long, value_delimiter = ',', default_value = "all")]
pub operations: Vec<HttpMethod>,
#[arg(long, default_value = "*")]
pub resource_pattern: String,
#[arg(long)]
pub max_size: Option<u64>,
#[arg(long, value_delimiter = ',', default_value = "*")]
pub allowed_content_type: Vec<String>,
}
pub fn exec(cmd: Command) {
match cmd {
Command::Generate(args) => generate_jwt(args),
Command::Verify => verify_jwt(),
}
.map_err(|e| e.exit_now())
.unwrap()
}
fn generate_jwt(args: GenerateArgs) -> Result<(), CliError> {
let jwt_config = app_config::server()
.auth()
.jwt_config_builder()
.clone()
.build()
.map_err(|e| e.exit_now())
.unwrap();
let validation_config = &jwt_config.validation;
let iss = if args.issue_as.is_some() {
args.issue_as
} else {
validation_config.iss.as_ref().and_then(|issuers| {
let issuers_vec: Vec<_> = issuers.iter().collect();
issuers_vec
.get(rand::random_range(0..issuers_vec.len()))
.map(|s| (*s).clone())
})
};
let aud = if args.audiences.is_some() {
args.audiences.unwrap()
} else {
validation_config
.aud
.as_ref()
.map(|aud| aud.iter().cloned().collect())
.unwrap_or_default()
};
let payload = Permission::new_minimum()
.permit_method(args.operations)
.permit_access_url(args.resource_pattern)
.restrict_maximum_size_option(args.max_size)
.permit_content_type(args.allowed_content_type);
let claims = Jwt::new(payload)
.issue_as_option(iss.as_ref())
.audiences(&aud)
.expires_in(Duration::seconds(args.exp_offset))
.not_valid_in(Duration::seconds(args.nbf_offset));
let token = Jwt::encode(&claims, &jwt_config)
.map_err(|e| CliError::new(ErrorKind::Io, format!("JWT encoding failed: {e}"), None))?;
println!("{}", token);
Ok(())
}
fn verify_jwt() -> Result<(), CliError> {
let mut token = String::new();
io::stdin().read_to_string(&mut token).map_err(|e| {
CliError::new(
ErrorKind::Io,
format!("Nothing to read from standard input as token input: {e}"),
None,
)
})?;
let token = token.trim();
if token.is_empty() {
return Err(CliError::new(
ErrorKind::Io,
"No token received from standard input.".to_string(),
None,
));
}
let jwt_config = app_config::server()
.auth()
.jwt_config_builder()
.clone()
.build()
.map_err(|e| e.exit_now())
.unwrap();
let decoded = Jwt::<Permission>::decode_unchecked(token).map_err(CliError::from)?;
let pretty_json = serde_json::to_string_pretty(&decoded).map_err(CliError::from)?;
match Jwt::<Permission>::decode(token, &jwt_config) {
Ok(_) => println!("Token verified successfully. Payload (Claims):\n"),
Err(e) => println!("Token invalid because of {e}. Payload (Claims):\n"),
}
println!("{}", pretty_json);
Ok(())
}