use anyhow::Error;
use authzen_session::*;
use chrono::{Duration, Utc};
use clap::Parser;
use jsonwebtoken::{Algorithm, DecodingKey, EncodingKey, Header, Validation};
use serde_json::Value;
use std::borrow::Cow;
use std::path::Path;
use uuid::Uuid;
#[derive(Clone, Debug, Parser)]
#[clap(author, version, about, long_about = None, trailing_var_arg=true)]
struct Args {
#[clap(long, env = "JWT_ACCOUNT_ID")]
account_id: String,
#[clap(short, long = "alg", env = "JWT_ALGORITHM")]
algorithm: Algorithm,
#[clap(short, long = "dur", env = "JWT_DURATION")]
duration: u32,
#[clap(long = "field")]
fields: Vec<String>,
#[clap(short, long = "iss", env = "JWT_ISSUER")]
issuer: String,
#[clap(long = "private-key", env = "JWT_PRIVATE_CERTIFICATE")]
jwt_private_certificate: String,
#[clap(long = "public-key", env = "JWT_PUBLIC_CERTIFICATE")]
jwt_public_certificate: String,
}
fn main() -> Result<(), Error> {
let Args {
account_id,
algorithm,
duration,
fields: named_fields,
issuer,
jwt_private_certificate,
jwt_public_certificate,
} = Args::parse();
let account_id: Value = serde_json::from_str(&account_id)?;
let jwt_private_certificate: EncodingKey =
parse_encoding_key(find_and_parse_pem(&jwt_private_certificate, &["PRIVATE KEY"])?); let jwt_public_certificate: DecodingKey =
parse_decoding_key(find_and_parse_pem(&jwt_public_certificate, &["PUBLIC KEY"])?);
let mut fields = serde_json::Map::default();
for named_field in named_fields {
let (name, value) = named_field.split_once('=').ok_or_else(|| Error::msg(format!("invalid field arg, must be of format `--field=<key>=<value>` where value is a serialized json value: found {named_field}")))?;
let value = serde_json::from_str(value).map_err(|err| Error::msg(format!("invalid field arg, must be of format `--field=<key>=<value>` where value is a serialized json value: found {named_field}: {err}")))?;
fields.insert(name.to_string(), value);
}
let claims = AccountSessionClaims::new(
AccountSessionState {
account_id,
fields: Value::Object(fields),
},
issuer.clone(),
Utc::now().naive_utc() + Duration::minutes(duration as i64),
);
let encoded = claims.encode(&Header::new(algorithm), &jwt_private_certificate)?;
let validation = {
let mut validation = Validation::new(algorithm);
validation.set_issuer(&[&issuer]);
validation.set_required_spec_claims(&["exp", "iss"]);
validation
};
<Session<AccountSessionToken<()>> as RawSession<AccountSession<Value, Value>>>::try_decode(
Session {
session_id: Uuid::new_v4(),
created_at: Utc::now().naive_utc(),
value: AccountSessionToken {
token: encoded.token.clone(),
claims: (),
},
max_age: None,
expires: None,
},
&jwt_public_certificate,
&validation,
)?;
println!("{}", encoded.token);
Ok(())
}
fn find_and_parse_pem<'a>(arg: &'a str, searches: &[&str]) -> Result<Cow<'a, str>, Error> {
let mut all_searches_match = true;
for search in searches {
if !arg.contains(search) {
all_searches_match = false;
break;
}
}
if all_searches_match {
return Ok(Cow::Borrowed(arg));
}
let path = Path::new(arg);
if !path.try_exists()? {
return Err(Error::msg("private key could not be found"));
}
Ok(Cow::Owned(String::from_utf8(std::fs::read(path)?)?))
}