1use basekit::base64::{encode_v1, Base64EncodeConfig, ALPHABET_BASE64_URL, PADDING_BASE64};
2use bytes::Bytes;
3use lazy_static::lazy_static;
4
5pub mod header;
6pub mod payload;
7pub mod signer;
8
9use header::Header;
10use payload::Payload;
11use signer::Signer;
12
13lazy_static! {
14 static ref B64_CONFIG: Base64EncodeConfig =
15 Base64EncodeConfig::new(&ALPHABET_BASE64_URL, PADDING_BASE64);
16}
17
18#[derive(Debug)]
19pub struct Jwt {
20 pub bytes: Bytes,
21 pub header: Header,
22 pub header_bytes: Bytes,
23 pub payload: Payload,
24 pub payload_bytes: Bytes,
25 pub signature: Vec<u8>,
26 pub signature_bytes: Bytes,
27}
28
29#[derive(Debug, Clone)]
30pub struct JwtBuilder {
31 pub payload: Payload,
32}
33
34impl JwtBuilder {
35 pub fn build(self, signer: &Signer) -> Jwt {
36 let signer = signer.clone();
37
38 let header = signer.header();
39 let header_json = serde_json::to_string(&header).unwrap();
40 let payload_json = serde_json::to_string(&self.payload).unwrap();
41
42 let header_encoded = encode_v1(&B64_CONFIG, header_json.as_bytes());
43 let payload_encoded = encode_v1(&B64_CONFIG, payload_json.as_bytes());
44
45 let header_encoded_str = String::from_utf8(header_encoded)
46 .unwrap()
47 .trim_end_matches('=')
48 .to_string();
49 let payload_encoded_str = String::from_utf8(payload_encoded)
50 .unwrap()
51 .trim_end_matches('=')
52 .to_string();
53
54 let header_num_bytes = header_encoded_str.len();
55 let payload_num_bytes = payload_encoded_str.len();
56
57 let mut signing_input = header_encoded_str.clone();
58 signing_input.push('.');
59 signing_input.push_str(&payload_encoded_str);
60
61 let signature = signer.signature(signing_input.as_bytes());
62 let signature_encoded = encode_v1(&B64_CONFIG, &signature);
63 let signature_encoded_str = String::from_utf8(signature_encoded)
64 .unwrap()
65 .trim_end_matches('=')
66 .to_string();
67 let signature_num_bytes = signature_encoded_str.len();
68
69 let mut jwt_string = header_encoded_str;
70 jwt_string.push('.');
71 jwt_string.push_str(&payload_encoded_str);
72 jwt_string.push('.');
73 jwt_string.push_str(&signature_encoded_str);
74
75 let bytes = Bytes::from(jwt_string.into_bytes());
76
77 let header_bytes = bytes.slice(0..header_num_bytes);
78 let payload_bytes =
79 bytes.slice((header_num_bytes + 1)..(header_num_bytes + payload_num_bytes + 1));
80 let signature_bytes = bytes.slice(
81 (header_num_bytes + payload_num_bytes + 2)
82 ..(header_num_bytes + payload_num_bytes + signature_num_bytes + 2),
83 );
84
85 Jwt {
86 bytes,
87 header,
88 header_bytes,
89 payload: self.payload,
90 payload_bytes,
91 signature,
92 signature_bytes,
93 }
94 }
95}
96
97#[cfg(test)]
98mod jwt_builder_tests {
99 use super::*;
100 use bytes::Bytes;
101 use hmac::{Hmac, Mac};
102 use sha2::Sha256;
103
104 #[test]
105 fn new() {
106 let mut payload = Payload::new();
107 payload.issuer = Some(String::from("hello").into());
108 payload.subject = Some(true.into());
109
110 let jwt_builder = JwtBuilder { payload };
111
112 let signer =
113 Signer::HmacSha256(Hmac::<Sha256>::new_from_slice(b"your-256-bit-secret").unwrap());
114
115 let jwt = jwt_builder.build(&signer);
116
117 assert_eq!(jwt.bytes, Bytes::from("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJoZWxsbyIsInN1YiI6dHJ1ZX0.Xk_NUrp8IZ4mvrATTB67AlpBmWDTufz6JHFpz_13KZg"));
118 }
119}