1use base64ct::{Base64UrlUnpadded, Encoding};
2use ring::hmac::{self, HMAC_SHA256};
3
4#[derive(Debug)]
5pub enum DecodeError {
6 InvalidToken,
7 InvalidSignature,
8}
9
10impl From<base64ct::Error> for DecodeError {
11 fn from(_: base64ct::Error) -> Self {
12 Self::InvalidToken
13 }
14}
15
16pub struct Tokens {
18 key: hmac::Key,
19}
20
21impl Tokens {
22 pub fn new(secret: &[u8]) -> Self {
27 let key = hmac::Key::new(HMAC_SHA256, secret);
29 Self { key }
30 }
31
32 pub fn encode(&self, value: &[u8]) -> String {
37 let msg = Base64UrlUnpadded::encode_string(value);
39
40 let sig = hmac::sign(&self.key, value);
42 let sig = Base64UrlUnpadded::encode_string(sig.as_ref());
43
44 [msg, sig].join(".")
46 }
47
48 pub fn decode(&self, token: &str) -> Result<Vec<u8>, DecodeError> {
52 let (msg, sig) = match token.split_once('.') {
54 Some(value) => value,
55 None => return Err(DecodeError::InvalidToken),
56 };
57
58 let msg: Vec<u8> = Base64UrlUnpadded::decode_vec(msg)?;
60 let sig: Vec<u8> = Base64UrlUnpadded::decode_vec(sig)?;
61
62 if hmac::verify(&self.key, &msg, &sig).is_err() {
64 return Err(DecodeError::InvalidSignature);
65 }
66
67 Ok(msg)
69 }
70}