use base64ct::{Base64UrlUnpadded, Encoding};
use ring::hmac::{self, HMAC_SHA256};
use serde::{de::DeserializeOwned, ser::Serialize};
pub use serde_json::Error as JsonError;
#[derive(Debug)]
pub enum DecodeError {
InvalidToken,
InvalidSignature,
Json(JsonError),
}
impl From<JsonError> for DecodeError {
fn from(value: JsonError) -> Self {
Self::Json(value)
}
}
impl From<base64ct::Error> for DecodeError {
fn from(_: base64ct::Error) -> Self {
Self::InvalidToken
}
}
pub struct Tokens {
key: hmac::Key,
}
impl Tokens {
pub fn new(secret: &[u8]) -> Self {
let key = hmac::Key::new(HMAC_SHA256, secret);
Self { key }
}
pub fn encode<T: Serialize>(&self, claims: &T) -> Result<String, JsonError> {
let msg_bytes = serde_json::to_vec(claims)?;
let msg = Base64UrlUnpadded::encode_string(&msg_bytes);
let sig = hmac::sign(&self.key, &msg_bytes);
let sig = Base64UrlUnpadded::encode_string(sig.as_ref());
Ok([msg, sig].join("."))
}
pub fn decode<T: DeserializeOwned>(&self, token: &str) -> Result<T, DecodeError> {
let (msg, sig) = match token.split_once('.') {
Some(value) => value,
None => return Err(DecodeError::InvalidToken),
};
let msg: Vec<u8> = Base64UrlUnpadded::decode_vec(msg)?;
let sig: Vec<u8> = Base64UrlUnpadded::decode_vec(sig)?;
if hmac::verify(&self.key, &msg, &sig).is_err() {
return Err(DecodeError::InvalidSignature);
}
let claims: T = serde_json::from_slice(&msg)?;
Ok(claims)
}
}