use axum::{Json, Router, routing::get};
use base64::Engine as _;
use jsonwebtoken::{Algorithm, EncodingKey, Header};
const KEY_ID: &str = "aion-test-key";
const SECRET: &[u8] = b"aion-test-jwt-shared-secret";
pub(crate) fn serve_jwks() -> Result<String, std::io::Error> {
let listener = std::net::TcpListener::bind(("127.0.0.1", 0))?;
listener.set_nonblocking(true)?;
let address = listener.local_addr()?;
let listener = tokio::net::TcpListener::from_std(listener)?;
tokio::spawn(async move {
let router = Router::new().route("/jwks.json", get(jwks_document));
if let Err(error) = axum::serve(listener, router).await {
tracing::warn!(%error, "fixture jwks server exited with error");
}
});
Ok(format!("http://{address}/jwks.json"))
}
async fn jwks_document() -> Json<serde_json::Value> {
Json(serde_json::json!({
"keys": [{
"kty": "oct",
"kid": KEY_ID,
"alg": "HS256",
"k": base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(SECRET),
}]
}))
}
pub(crate) fn mint_token(
subject: &str,
namespace: &str,
) -> Result<String, jsonwebtoken::errors::Error> {
mint(
subject,
namespace,
jsonwebtoken::get_current_timestamp() + 3600,
)
}
pub(crate) fn mint_expired_token(
subject: &str,
namespace: &str,
) -> Result<String, jsonwebtoken::errors::Error> {
mint(
subject,
namespace,
jsonwebtoken::get_current_timestamp().saturating_sub(3600),
)
}
fn mint(subject: &str, namespace: &str, exp: u64) -> Result<String, jsonwebtoken::errors::Error> {
let mut header = Header::new(Algorithm::HS256);
header.kid = Some(KEY_ID.to_owned());
let claims = serde_json::json!({
"sub": subject,
"namespace": namespace,
"exp": exp,
});
jsonwebtoken::encode(&header, &claims, &EncodingKey::from_secret(SECRET))
}