axum_security/jwt/
builder.rs1use std::{borrow::Cow, error::Error, fmt::Display, marker::PhantomData, sync::Arc};
2
3use axum::http::{HeaderName, header::AUTHORIZATION};
4#[cfg(feature = "cookie")]
5use cookie_monster::CookieBuilder;
6use jsonwebtoken::{DecodingKey, EncodingKey, Header, Validation};
7
8#[cfg(feature = "cookie")]
9use crate::cookie_options::CookieOptionsBuilder;
10use crate::{
11 jwt::{ExtractFrom, JwtContext, JwtContextInner},
12 utils::get_env,
13};
14
15static PREFIX_BEARER: &str = "Bearer ";
16static PREFIX_NONE: &str = "";
17
18pub struct JwtContextBuilder {
27 encoding_key: Option<EncodingKey>,
28 decoding_key: Option<DecodingKey>,
29 jwt_header: Header,
30 validation: Validation,
31 extract: ExtractFromBuilder,
32}
33
34impl Default for JwtContextBuilder {
35 fn default() -> Self {
36 Self::new()
37 }
38}
39
40impl JwtContextBuilder {
41 pub fn new() -> Self {
42 JwtContextBuilder {
43 encoding_key: None,
44 decoding_key: None,
45 jwt_header: Header::default(),
46 validation: Validation::default(),
47 extract: ExtractFromBuilder::header_with_prefix(AUTHORIZATION, PREFIX_BEARER),
48 }
49 }
50
51 pub fn encoding_key(mut self, encoding_key: EncodingKey) -> Self {
52 self.encoding_key = Some(encoding_key);
53 self
54 }
55
56 pub fn decoding_key(mut self, decoding_key: DecodingKey) -> Self {
57 self.decoding_key = Some(decoding_key);
58 self
59 }
60
61 pub fn jwt_secret(self, jwt_secret: impl AsRef<[u8]>) -> Self {
62 let jwt_secret = jwt_secret.as_ref();
63 self.encoding_key(EncodingKey::from_secret(jwt_secret))
64 .decoding_key(DecodingKey::from_secret(jwt_secret))
65 }
66
67 pub fn jwt_secret_env(self, name: &str) -> Self {
68 self.jwt_secret(get_env(name))
69 }
70
71 pub fn validation(mut self, validation: Validation) -> Self {
72 self.validation = validation;
73 self
74 }
75
76 pub fn jwt_header(mut self, header: Header) -> Self {
77 self.jwt_header = header;
78 self
79 }
80
81 pub fn extract_header_with_prefix(
82 mut self,
83 header: impl AsRef<[u8]>,
84 prefix: impl Into<Cow<'static, str>>,
85 ) -> Self {
86 self.extract = ExtractFromBuilder::header_with_prefix(
87 HeaderName::from_bytes(header.as_ref())
88 .expect("header value contains invalid characters"),
89 prefix.into(),
90 );
91 self
92 }
93
94 pub fn extract_header(mut self, header: impl AsRef<str>) -> Self {
95 self.extract = ExtractFromBuilder::header_with_prefix(
96 HeaderName::from_bytes(header.as_ref().as_bytes())
97 .expect("header value contains invalid characters"),
98 PREFIX_NONE,
99 );
100 self
101 }
102
103 #[cfg(feature = "cookie")]
104 pub fn extract_cookie(mut self, cookie_name: impl Into<Cow<'static, str>>) -> Self {
105 self.extract = ExtractFromBuilder::cookie(cookie_name.into());
106 self
107 }
108
109 #[cfg(feature = "cookie")]
110 pub fn use_dev_cookie(mut self, dev_mode: bool) -> Self {
111 self.extract = self.extract.with_cookie(|mut c| {
112 c.dev = dev_mode;
113 c
114 });
115 self
116 }
117
118 #[cfg(feature = "cookie")]
119 pub fn cookie(mut self, f: impl FnOnce(CookieBuilder) -> CookieBuilder) -> Self {
120 self.extract = self.extract.with_cookie(|mut c| {
121 c.cookie = (f)(c.cookie);
122 c
123 });
124 self
125 }
126
127 #[cfg(feature = "cookie")]
128 pub fn dev_cookie(mut self, f: impl FnOnce(CookieBuilder) -> CookieBuilder) -> Self {
129 self.extract = self.extract.with_cookie(|mut c| {
130 c.dev_cookie = (f)(c.dev_cookie);
131 c
132 });
133 self
134 }
135
136 pub fn try_build<T>(self) -> Result<JwtContext<T>, JwtBuilderError> {
137 let encoding_key = self
138 .encoding_key
139 .ok_or(JwtBuilderError::EncodingKeyMissing)?;
140
141 let decoding_key = self
142 .decoding_key
143 .ok_or(JwtBuilderError::DecodingKeyMissing)?;
144
145 let extract = self.extract.into_extract();
146
147 Ok(JwtContext(Arc::new(JwtContextInner {
148 encoding_key,
149 decoding_key,
150 jwt_header: self.jwt_header,
151 validation: self.validation,
152 extract,
153 data: PhantomData,
154 })))
155 }
156
157 pub fn build<T>(self) -> JwtContext<T> {
158 self.try_build().unwrap()
159 }
160}
161
162#[derive(Debug)]
164pub enum JwtBuilderError {
165 EncodingKeyMissing,
167 DecodingKeyMissing,
169}
170
171impl Display for JwtBuilderError {
172 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
173 match self {
174 JwtBuilderError::EncodingKeyMissing => f.write_str("Encoding key is missing"),
175 JwtBuilderError::DecodingKeyMissing => f.write_str("Decoding key is missing"),
176 }
177 }
178}
179
180impl Error for JwtBuilderError {}
181
182pub(crate) enum ExtractFromBuilder {
183 #[cfg(feature = "cookie")]
184 Cookie(Box<CookieOptionsBuilder>),
185 Header {
186 header: HeaderName,
187 prefix: Cow<'static, str>,
188 },
189}
190
191impl ExtractFromBuilder {
192 #[cfg(feature = "cookie")]
193 fn cookie(name: Cow<'static, str>) -> Self {
194 let mut builder = CookieOptionsBuilder::new();
195 builder.set_name(name);
196 ExtractFromBuilder::Cookie(builder.into())
197 }
198
199 fn into_extract(self) -> ExtractFrom {
200 match self {
201 #[cfg(feature = "cookie")]
202 ExtractFromBuilder::Cookie(builder) => ExtractFrom::Cookie(builder.build().into()),
203 ExtractFromBuilder::Header { header, prefix } => ExtractFrom::Header { header, prefix },
204 }
205 }
206
207 #[cfg(feature = "cookie")]
208 fn with_cookie(self, f: impl FnOnce(CookieOptionsBuilder) -> CookieOptionsBuilder) -> Self {
209 match self {
210 ExtractFromBuilder::Cookie(cookie) => ExtractFromBuilder::Cookie(f(*cookie).into()),
211 this => this,
212 }
213 }
214
215 fn header_with_prefix(header: HeaderName, prefix: impl Into<Cow<'static, str>>) -> Self {
216 ExtractFromBuilder::Header {
217 header,
218 prefix: prefix.into(),
219 }
220 }
221}
222
223#[cfg(test)]
224mod jwt_builder {
225 use crate::jwt::{DecodingKey, EncodingKey, JwtBuilderError, JwtContext};
226
227 #[test]
228 fn encoding_key_missing() {
229 let result = JwtContext::builder()
230 .decoding_key(DecodingKey::from_secret(b"test"))
231 .try_build::<()>();
232
233 assert!(matches!(result, Err(JwtBuilderError::EncodingKeyMissing)));
234 }
235
236 #[test]
237 fn decoding_key_missing() {
238 let result = JwtContext::builder()
239 .encoding_key(EncodingKey::from_secret(b"test"))
240 .try_build::<()>();
241
242 assert!(matches!(result, Err(JwtBuilderError::DecodingKeyMissing)));
243 }
244}