use crate::auth::error::AuthError;
use jsonwebtoken::DecodingKey;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Jwk {
#[serde(skip_serializing_if = "Option::is_none")]
pub kid: Option<String>,
pub kty: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub alg: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub n: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub e: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub crv: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub x: Option<String>,
}
impl Jwk {
pub fn to_decoding_key(&self) -> Result<DecodingKey, AuthError> {
match self.kty.as_str() {
"RSA" => {
let n = self
.n
.as_ref()
.ok_or_else(|| AuthError::Token("Missing 'n' component in JWK".to_string()))?;
let e = self
.e
.as_ref()
.ok_or_else(|| AuthError::Token("Missing 'e' component in JWK".to_string()))?;
DecodingKey::from_rsa_components(n, e).map_err(|e| AuthError::Token(e.to_string()))
}
"OKP" => {
match self.crv.as_deref() {
Some("Ed25519") => {}
Some(other) => {
return Err(AuthError::Token(format!(
"Unsupported OKP curve '{}' in JWK",
other
)));
}
None => {
return Err(AuthError::Token(
"Missing 'crv' component in OKP JWK".to_string(),
));
}
}
let x_str = self
.x
.as_ref()
.ok_or_else(|| AuthError::Token("Missing 'x' component in JWK".to_string()))?;
authkestra_crypto_util::parse_ed25519_verifying_key_strict(x_str)
.map_err(|e| AuthError::Token(e.to_string()))?;
DecodingKey::from_ed_components(x_str).map_err(|e| AuthError::Token(e.to_string()))
}
other => Err(AuthError::Token(format!(
"Unsupported JWK 'kty' '{}' — only RSA and OKP are supported",
other
))),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rejects_low_order_ed25519_key() {
let identity_b64 = "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
let jwk = Jwk {
kid: None,
kty: "OKP".to_string(),
alg: None,
n: None,
e: None,
crv: Some("Ed25519".to_string()),
x: Some(identity_b64.to_string()),
};
let err = jwk
.to_decoding_key()
.expect_err("should reject low order point");
assert!(
err.to_string().contains("low-order"),
"expected low-order point rejection, got: {}",
err
);
}
}