use jsonwebtoken::{errors as jwterrors, Algorithm, EncodingKey, Header};
use digest::Digest;
use rustc_hex::ToHex;
use serde::{Deserialize, Serialize};
use sha2::Sha256;
use std::time::{SystemTime, UNIX_EPOCH};
use thiserror::Error;
const EXPIRY: u64 = 55;
#[derive(Debug, Clone)]
pub struct JwtSigner {
pub key: EncodingKey,
pub api_key: String,
}
impl JwtSigner {
pub fn new(key: EncodingKey, api_key: &str) -> Self {
Self {
key,
api_key: api_key.to_string(),
}
}
pub fn sign<S: Serialize>(&self, path: &str, body: S) -> Result<String, JwtError> {
let header = Header::new(Algorithm::RS256);
let claims = Claims::new(path, &self.api_key, body)?;
Ok(jsonwebtoken::encode(&header, &claims, &self.key)?)
}
}
#[derive(Debug, Deserialize, Serialize)]
struct Claims<'a> {
uri: &'a str,
nonce: u64,
iat: u64,
exp: u64,
sub: &'a str,
#[serde(rename = "bodyHash")]
body_hash: String,
}
#[derive(Debug, Error)]
pub enum JwtError {
#[error("Could not serialize JWT body: {0}")]
Json(#[from] serde_json::Error),
#[error("Could not create JWT time: {0}")]
Time(#[from] std::time::SystemTimeError),
#[error(transparent)]
Jwt(#[from] jwterrors::Error),
}
impl<'a> Claims<'a> {
fn new<S: Serialize>(uri: &'a str, sub: &'a str, body: S) -> Result<Self, JwtError> {
let now = SystemTime::now().duration_since(UNIX_EPOCH)?.as_millis() as u64;
let nonce = now;
let now = now / 1000;
let body_hash = {
let mut digest = Sha256::new();
digest.update(serde_json::to_vec(&body)?);
digest.finalize().to_vec()
};
Ok(Self {
uri,
sub,
body_hash: body_hash.to_hex::<String>(),
nonce,
iat: now,
exp: now + EXPIRY,
})
}
}