use std::str::FromStr;
use anyhow::{Result, bail};
use jsonwebtoken::{Algorithm, EncodingKey, Header};
use super::iterator::option_inner;
use super::json_bridge::pvalue_to_json;
use super::native_methods::value_to_bytes;
use super::value::{StructData, Value};
pub(super) fn jwt_algorithm(ty: &str, variant: &str) -> Option<Value> {
if ty != "Algorithm" || Algorithm::from_str(variant).is_err() {
return None;
}
Some(Value::enum_of("Algorithm", variant, Vec::new()))
}
pub(super) fn jwt_assoc(ty: &str, func: &str, args: &[Value]) -> Result<Option<Value>> {
Ok(Some(match (ty, func) {
("Header", "new" | "default") => {
let alg = match args.first() {
Some(v) => v.clone(),
None => jwt_algorithm("Algorithm", "HS256").expect("HS256 is a known algorithm"),
};
Value::struct_of(
"Header",
[
("alg".into(), alg),
("typ".into(), Value::some(Value::str("JWT"))),
("kid".into(), Value::none()),
("cty".into(), Value::none()),
],
)
}
("EncodingKey", "from_secret") => key_value("secret", args),
("EncodingKey", "from_ec_pem") => {
match EncodingKey::from_ec_pem(&value_to_bytes(args.first())) {
Ok(_) => Value::ok(key_value("ec_pem", args)),
Err(e) => Value::err(Value::str(e.to_string())),
}
}
_ => return Ok(None),
}))
}
fn key_value(kind: &str, args: &[Value]) -> Value {
Value::struct_of(
"EncodingKey",
[
("kind".into(), Value::str(kind)),
("data".into(), args.first().cloned().unwrap_or(Value::Unit)),
],
)
}
pub(super) fn jwt_encode(args: &[Value]) -> Result<Value> {
let (Some(Value::Struct(header)), Some(claims), Some(Value::Struct(key))) =
(args.first(), args.get(1), args.get(2))
else {
bail!("encode takes a header, claims, and an encoding key");
};
let mut real = Header::new(header_algorithm(header)?);
real.typ = opt_string(header, "typ");
real.kid = opt_string(header, "kid");
real.cty = opt_string(header, "cty");
let bytes = value_to_bytes(key.get("data").as_ref());
let kind = key.get("kind").map(|v| v.display()).unwrap_or_default();
let real_key = match kind.as_str() {
"secret" => EncodingKey::from_secret(&bytes),
"ec_pem" => match EncodingKey::from_ec_pem(&bytes) {
Ok(k) => k,
Err(e) => return Ok(Value::err(Value::str(e.to_string()))),
},
other => bail!("`{other}` is not an EncodingKey"),
};
Ok(
match jsonwebtoken::encode(&real, &pvalue_to_json(claims)?, &real_key) {
Ok(token) => Value::ok(Value::str(token)),
Err(e) => Value::err(Value::str(e.to_string())),
},
)
}
fn header_algorithm(s: &StructData) -> Result<Algorithm> {
let Some(Value::Enum { variant, .. }) = s.get("alg") else {
bail!("the header has no algorithm");
};
match Algorithm::from_str(&variant) {
Ok(a) => Ok(a),
Err(_) => bail!("unknown JWT algorithm `{variant}`"),
}
}
fn opt_string(s: &StructData, field: &str) -> Option<String> {
option_inner(&s.get(field)?).map(|v| v.display())
}