kvarn_auth/
lib.rs

1#![doc = include_str!("../README.md")]
2// See https://doc.rust-lang.org/beta/unstable-book/language-features/doc-cfg.html & https://github.com/rust-lang/rust/pull/89596
3#![cfg_attr(docsrs, feature(doc_auto_cfg))]
4#![cfg_attr(docsrs, feature(doc_cfg))]
5#![allow(dead_code)]
6#![deny(missing_docs)]
7use std::borrow::Cow;
8use std::future::Future;
9use std::net::{IpAddr, SocketAddr};
10use std::sync::Arc;
11use std::time::{Duration, SystemTime, UNIX_EPOCH};
12
13#[cfg(not(feature = "structured"))]
14mod unescape;
15
16#[cfg(feature = "integration-fs")]
17pub mod integrations;
18
19use base64::Engine;
20use futures::FutureExt;
21
22#[cfg(any(feature = "ecdsa", feature = "hmac"))]
23use rand::Rng;
24#[cfg(any(feature = "ecdsa", feature = "rsa", feature = "hmac"))]
25use sha2::Digest;
26
27#[cfg(feature = "structured")]
28use serde::{de::DeserializeOwned, Serialize};
29
30#[cfg(feature = "chacha20")]
31use chacha20::cipher::{KeyIvInit, StreamCipher};
32#[cfg(feature = "hmac")]
33use hmac::{Hmac, Mac};
34#[cfg(feature = "ecdsa")]
35use p256::ecdsa::signature::{Signer, Verifier};
36#[cfg(feature = "rsa")]
37use rsa::RsaPublicKey;
38
39#[cfg(feature = "chacha20")]
40pub use chacha20;
41#[cfg(feature = "hmac")]
42pub use hmac;
43#[cfg(feature = "ecdsa")]
44pub use p256;
45#[cfg(feature = "rsa")]
46pub use rsa;
47
48#[cfg(not(any(feature = "ecdsa", feature = "rsa", feature = "hmac")))]
49compile_error!("At least one algorithm has to be enabled.");
50
51/// Trait to allow type bounds when serde isn't enabled.
52#[cfg(not(feature = "structured"))]
53pub trait Serialize {}
54#[cfg(not(feature = "structured"))]
55impl<T> Serialize for T {}
56/// Trait to allow type bounds when serde isn't enabled.
57#[cfg(not(feature = "structured"))]
58pub trait DeserializeOwned {}
59#[cfg(not(feature = "structured"))]
60impl<T> DeserializeOwned for T {}
61
62const BASE64_ENGINE: base64::engine::general_purpose::GeneralPurpose =
63    base64::engine::general_purpose::GeneralPurpose::new(
64        &base64::alphabet::URL_SAFE,
65        base64::engine::general_purpose::GeneralPurposeConfig::new()
66            .with_encode_padding(false)
67            .with_decode_padding_mode(base64::engine::DecodePaddingMode::Indifferent),
68    );
69
70fn seconds_since_epoch() -> u64 {
71    SystemTime::now()
72        .duration_since(UNIX_EPOCH)
73        .unwrap()
74        .as_secs()
75}
76
77fn get_cookie<'a, T>(req: &'a kvarn::prelude::Request<T>, name: &str) -> Option<(&'a str, usize)> {
78    get_cookie_with_header_pos(req, name).map(|(c, p, _)| (c, p))
79}
80fn get_cookie_with_header_pos<'a, T>(
81    req: &'a kvarn::prelude::Request<T>,
82    name: &str,
83) -> Option<(&'a str, usize, usize)> {
84    let mut cookie = None;
85    let filter = format!("{name}=");
86    for (header_pos, header) in req
87        .headers()
88        .get_all("cookie")
89        .into_iter()
90        .enumerate()
91        .filter_map(|(p, h)| h.to_str().ok().map(|h| (p, h)))
92    {
93        if let Some(pos) = header.find(&filter) {
94            cookie = Some((header, pos + filter.len(), header_pos));
95            break;
96        }
97    }
98    cookie
99}
100fn extract_cookie_value(d: (&str, usize)) -> &str {
101    let s = &d.0[d.1..];
102    s.split(';').next().unwrap_or(s)
103}
104fn remove_cookie(req: &mut kvarn::FatRequest, cookie_name: &str) -> bool {
105    use kvarn::prelude::*;
106    if let Some((cookie, pos, header_pos)) = get_cookie_with_header_pos(req, cookie_name) {
107        let value_start = pos - cookie_name.len() - 1;
108        let value_end = cookie[value_start..]
109            .find("; ")
110            .map(|v| v + 2)
111            .unwrap_or_else(|| cookie.len() - value_start)
112            + value_start;
113        let mut new_cookie_header = cookie.to_owned();
114        new_cookie_header.drain(value_start..value_end);
115        let header_to_change = req.headers_mut().entry("cookie");
116        if let header::Entry::Occupied(mut entry) = header_to_change {
117            let header_to_change = entry.iter_mut().nth(header_pos).unwrap();
118            *header_to_change = HeaderValue::from_str(&new_cookie_header)
119                .expect("unreachable, as we just removed bytes");
120        } else {
121            unreachable!(
122                "The header must be present, since we got the data from it in the previous call"
123            );
124        }
125        true
126    } else {
127        false
128    }
129}
130fn remove_set_cookie(
131    response: &mut kvarn::prelude::Response<kvarn::prelude::Bytes>,
132    cookie_name: &str,
133    cookie_path: &str,
134) {
135    let remove_cookie = format!(
136        "{cookie_name}=\"\"; \
137        Path={cookie_path}; \
138        Max-Age=0"
139    );
140    response.headers_mut().append(
141        "set-cookie",
142        kvarn::prelude::HeaderValue::from_str(&remove_cookie)
143            .expect("a user-supplied cookie_name or the cookie_path contains illegal bytes for use in a header"),
144    );
145}
146/// The data in the JWT.
147///
148/// This stores any data attached to a logged in user.
149/// This data is not secret - it can be ready by the receiver.
150/// The authenticity of the message is however always conserved (as long as your secret hasn't
151/// leaked).
152#[derive(Debug)]
153pub enum AuthData<T: Serialize + DeserializeOwned = ()> {
154    /// No data.
155    None,
156    /// Text data.
157    Text(String),
158    /// A number.
159    Number(f64),
160    /// Text and a number.
161    TextNumber(String, f64),
162    /// Fields `iat`, `exp`, and `__variant` are overriden and will not be visible when the
163    /// JWT is decoded.
164    ///
165    /// This panics when the serde feature is not enabled.
166    Structured(T),
167}
168#[cfg(feature = "hmac")]
169fn hmac_sha256(secret: &[u8], bytes: &[u8]) -> impl AsRef<[u8]> {
170    type HmacSha256 = Hmac<sha2::Sha256>;
171    // Hmac can take a key of any length
172    let mut hmac = HmacSha256::new_from_slice(secret).unwrap();
173    hmac.update(bytes);
174    hmac.finalize().into_bytes()
175}
176fn ip_to_bytes(ip: IpAddr, buf: &mut Vec<u8>) {
177    match ip {
178        IpAddr::V4(v4) => buf.extend(v4.octets()),
179        IpAddr::V6(v6) => buf.extend(v6.octets()),
180    }
181}
182impl<T: Serialize + DeserializeOwned> AuthData<T> {
183    /// # Panics
184    ///
185    /// Panics if a number is `NaN` or an infinity.
186    /// The structured data (if that's what this is) must not error when being serialized.
187    #[cfg(feature = "structured")]
188    fn into_jwt(
189        self,
190        signing_algo: &ComputedAlgo,
191        header: &[u8],
192        seconds_before_expiry: u64,
193        ip: Option<IpAddr>,
194    ) -> String {
195        let mut s = BASE64_ENGINE.encode(header);
196        let mut map = match self {
197            Self::None => {
198                let mut map = serde_json::Map::new();
199                map.insert("__variant".to_owned(), "e".into());
200                map
201            }
202            Self::Text(t) => {
203                let mut map = serde_json::Map::new();
204                map.insert("text".to_owned(), serde_json::Value::String(t));
205                map.insert("__variant".to_owned(), "t".into());
206                map
207            }
208            Self::Number(n) => {
209                let mut map = serde_json::Map::new();
210                map.insert(
211                    "num".to_owned(),
212                    serde_json::Value::Number(
213                        serde_json::Number::from_f64(n)
214                            .expect("JWTs cannot contain NaN or infinities"),
215                    ),
216                );
217                map.insert("__variant".to_owned(), "n".into());
218                map
219            }
220            Self::TextNumber(t, n) => {
221                let mut map = serde_json::Map::new();
222                map.insert("text".to_owned(), serde_json::Value::String(t));
223                map.insert(
224                    "num".to_owned(),
225                    serde_json::Value::Number(
226                        serde_json::Number::from_f64(n)
227                            .expect("JWTs cannot contain NaN or infinities"),
228                    ),
229                );
230                map.insert("__variant".to_owned(), "tn".into());
231                map
232            }
233            Self::Structured(t) => {
234                let mut v =
235                    serde_json::to_value(t).expect("failed to serialize structured auth data");
236                if let Some(map) = v.as_object_mut() {
237                    let mut map = core::mem::take(map);
238                    if map.contains_key("__variant") {
239                        log::warn!("`__variant` key in JWT payload will be overridden");
240                    }
241                    if map.contains_key("__deserialize_v") {
242                        log::warn!("`__deserialize_v` key in JWT payload will be overridden");
243                        map.insert("__deserialize_v".to_owned(), serde_json::Value::Bool(false));
244                    }
245                    map.insert("__variant".to_owned(), "s".into());
246                    map
247                } else {
248                    let mut map = serde_json::Map::new();
249                    map.insert("v".to_owned(), v);
250                    map.insert("__deserialize_v".to_owned(), serde_json::Value::Bool(true));
251                    map.insert("__variant".to_owned(), "s".into());
252                    map
253                }
254            }
255        };
256        if map.contains_key("iat") {
257            log::warn!("`iat` key in JWT payload will be overridden");
258        }
259        if map.contains_key("exp") {
260            log::warn!("`exp` key in JWT payload will be overridden");
261        }
262        let now = seconds_since_epoch();
263        map.insert("iat".to_owned(), serde_json::Value::Number(now.into()));
264        map.insert(
265            "exp".to_owned(),
266            serde_json::Value::Number((now + seconds_before_expiry).into()),
267        );
268        let value = serde_json::Value::Object(map);
269        let payload = value.to_string();
270        s.push('.');
271        BASE64_ENGINE.encode_string(payload.as_bytes(), &mut s);
272        match signing_algo {
273            #[cfg(feature = "hmac")]
274            ComputedAlgo::HmacSha256 { secret, .. } => {
275                // Hmac can take a key of any length
276                let mut hmac = Hmac::<sha2::Sha256>::new_from_slice(secret).unwrap();
277                hmac.update(s.as_bytes());
278                if let Some(ip) = ip {
279                    hmac.update(IpBytes::from(ip).as_ref());
280                }
281                let sig = hmac.finalize().into_bytes();
282                s.push('.');
283                BASE64_ENGINE.encode_string(sig, &mut s);
284            }
285            #[cfg(feature = "rsa")]
286            ComputedAlgo::RSASha256 {
287                private_key,
288                public_key: _,
289            } => {
290                let mut hasher = sha2::Sha256::new();
291                hasher.update(s.as_bytes());
292                if let Some(ip) = ip {
293                    hasher.update(IpBytes::from(ip).as_ref());
294                }
295                let hash = hasher.finalize();
296                let signature = private_key
297                    .sign(rsa::Pkcs1v15Sign::new::<sha2::Sha256>(), &hash)
298                    .expect("failed to sign JWT with RSA key");
299                s.push('.');
300                BASE64_ENGINE.encode_string(signature, &mut s);
301            }
302            #[cfg(feature = "ecdsa")]
303            ComputedAlgo::EcdsaP256 { private_key, .. } => {
304                let signature: p256::ecdsa::DerSignature = if let Some(ip) = ip {
305                    let mut v = s.as_bytes().to_vec();
306                    v.extend_from_slice(IpBytes::from(ip).as_ref());
307                    private_key.sign(&v)
308                } else {
309                    private_key.sign(s.as_bytes())
310                };
311                s.push('.');
312                BASE64_ENGINE.encode_string(signature, &mut s);
313            }
314        }
315        s
316    }
317    /// # Panics
318    ///
319    /// Panics if a number is `NaN` or an infinity.
320    /// The structured data (if that's what this is) must not error when being serialized.
321    #[cfg(not(feature = "structured"))]
322    fn into_jwt(
323        self,
324        signing_algo: &ComputedAlgo,
325        header: &[u8],
326        seconds_before_expiry: u64,
327        ip: Option<IpAddr>,
328    ) -> String {
329        let mut s = BASE64_ENGINE.encode(header);
330        let mut json = String::new();
331        json.push_str(r#"{"__variant":"#);
332        match self {
333            Self::None => {
334                json.push_str(r#""e","#);
335            }
336            Self::Text(t) => {
337                json.push_str(r#""t","text":""#);
338                json.push_str(&t.escape_default().to_string());
339                json.push_str("\",");
340            }
341            Self::Number(n) => {
342                json.push_str(r#""n","num":"#);
343                json.push_str(&n.to_string());
344                json.push(',');
345            }
346            Self::TextNumber(t, n) => {
347                json.push_str(r#""tn","text":""#);
348                json.push_str(&t.escape_default().to_string());
349                json.push_str("\",");
350                json.push_str(r#""num":"#);
351                json.push_str(&n.to_string());
352                json.push(',');
353            }
354            Self::Structured(_t) => {
355                panic!("Using AuthData::Structured without the serde feature enabled")
356            }
357        };
358        let now = seconds_since_epoch();
359        json.push_str(r#""iat":"#);
360        json.push_str(&now.to_string());
361        json.push(',');
362        json.push_str(r#""exp":"#);
363        json.push_str(&(now + seconds_before_expiry).to_string());
364        json.push('}');
365        let payload = json;
366        s.push('.');
367        BASE64_ENGINE.encode_string(payload.as_bytes(), &mut s);
368
369        match signing_algo {
370            #[cfg(feature = "hmac")]
371            ComputedAlgo::HmacSha256 { secret, .. } => {
372                // Hmac can take a key of any length
373                let mut hmac = Hmac::<sha2::Sha256>::new_from_slice(secret).unwrap();
374                hmac.update(s.as_bytes());
375                if let Some(ip) = ip {
376                    hmac.update(IpBytes::from(ip).as_ref());
377                }
378                let sig = hmac.finalize().into_bytes();
379                s.push('.');
380                BASE64_ENGINE.encode_string(sig, &mut s);
381            }
382            #[cfg(feature = "rsa")]
383            ComputedAlgo::RSASha256 {
384                private_key,
385                public_key: _,
386            } => {
387                let mut hasher = sha2::Sha256::new();
388                hasher.update(s.as_bytes());
389                if let Some(ip) = ip {
390                    hasher.update(IpBytes::from(ip).as_ref());
391                }
392                let hash = hasher.finalize();
393                let signature = private_key
394                    .sign(rsa::Pkcs1v15Sign::new::<sha2::Sha256>(), &hash)
395                    .expect("failed to sign JWT with RSA key");
396                s.push('.');
397                BASE64_ENGINE.encode_string(signature, &mut s);
398            }
399            #[cfg(feature = "ecdsa")]
400            ComputedAlgo::EcdsaP256 { private_key, .. } => {
401                let signature: p256::ecdsa::Signature = if let Some(ip) = ip {
402                    let mut v = s.as_bytes().to_vec();
403                    v.extend_from_slice(IpBytes::from(ip).as_ref());
404                    private_key.sign(&v)
405                } else {
406                    private_key.sign(s.as_bytes())
407                };
408                s.push('.');
409                BASE64_ENGINE.encode_string(signature.to_der(), &mut s);
410            }
411        }
412        s
413    }
414    /// # Panics
415    ///
416    /// See [`Self::into_jwt`].
417    fn into_jwt_with_default_header(
418        self,
419        signing_algo: &ComputedAlgo,
420        seconds_before_expiry: u64,
421        ip: Option<IpAddr>,
422    ) -> String {
423        static HS_HEADER: &[u8] = r#"{"alg":"HS256"}"#.as_bytes();
424        static RS_HEADER: &[u8] = r#"{"alg":"RS256"}"#.as_bytes();
425        static EP_HEADER: &[u8] = r#"{"alg":"ES256"}"#.as_bytes();
426        let header = match signing_algo {
427            #[cfg(feature = "hmac")]
428            ComputedAlgo::HmacSha256 { .. } => HS_HEADER,
429            #[cfg(feature = "rsa")]
430            ComputedAlgo::RSASha256 { .. } => RS_HEADER,
431            #[cfg(feature = "ecdsa")]
432            ComputedAlgo::EcdsaP256 { .. } => EP_HEADER,
433        };
434        self.into_jwt(signing_algo, header, seconds_before_expiry, ip)
435    }
436}
437/// The state of the user in question.
438#[derive(Debug)]
439pub enum Validation<T: Serialize + DeserializeOwned> {
440    /// This can come from multiple sources, including but not limited to:
441    /// - invalid base64 encoding
442    /// - invalid JWT structure
443    /// - mismatched hash (the user changed their privilege)
444    /// - serialization errors to the desired structured type
445    /// - unexpected data in the JSON
446    /// - failed to parse JSON
447    /// - expiry date is not included
448    Unauthorized,
449    /// The user is authorized with the provided data.
450    /// The data is guaranteed to be what you authorized.
451    Authorized(AuthData<T>),
452}
453
454enum IpBytes {
455    V4([u8; 4]),
456    V6([u8; 16]),
457}
458impl From<IpAddr> for IpBytes {
459    fn from(ip: IpAddr) -> Self {
460        match ip {
461            IpAddr::V4(ip) => Self::V4(ip.octets()),
462            IpAddr::V6(ip) => Self::V6(ip.octets()),
463        }
464    }
465}
466impl AsRef<[u8]> for IpBytes {
467    fn as_ref(&self) -> &[u8] {
468        match self {
469            Self::V4(addr) => addr,
470            Self::V6(addr) => addr,
471        }
472    }
473}
474
475trait Validate {
476    fn validate(&self, data: &[u8], signature: &[u8], ip: Option<IpAddr>) -> Result<(), ()>;
477}
478#[cfg(any(feature = "rsa", feature = "ecdsa"))]
479impl Validate for ValidationAlgo {
480    fn validate(&self, data: &[u8], signature: &[u8], ip: Option<IpAddr>) -> Result<(), ()> {
481        (&self).validate(data, signature, ip)
482    }
483}
484#[cfg(any(feature = "rsa", feature = "ecdsa"))]
485impl<'a> Validate for &'a ValidationAlgo {
486    #[allow(unused_variables)] // cfg
487    fn validate(&self, data: &[u8], signature: &[u8], ip: Option<IpAddr>) -> Result<(), ()> {
488        match *self {
489            #[cfg(feature = "rsa")]
490            ValidationAlgo::RSASha256 { public_key } => {
491                let mut hasher = sha2::Sha256::new();
492                hasher.update(data);
493                if let Some(ip) = ip {
494                    hasher.update(IpBytes::from(ip).as_ref());
495                }
496                let hash = hasher.finalize();
497                public_key
498                    .verify(rsa::Pkcs1v15Sign::new::<sha2::Sha256>(), &hash, signature)
499                    .map_err(|_| ())
500            }
501            #[cfg(feature = "ecdsa")]
502            ValidationAlgo::EcdsaP256 { public_key } => {
503                let sig = p256::ecdsa::Signature::from_der(signature).map_err(|_| ())?;
504                public_key.verify(data, &sig).map_err(|_| ())
505            }
506        }
507    }
508}
509impl Validate for ComputedAlgo {
510    fn validate(&self, data: &[u8], signature: &[u8], ip: Option<IpAddr>) -> Result<(), ()> {
511        (&self).validate(data, signature, ip)
512    }
513}
514impl<'a> Validate for &'a ComputedAlgo {
515    #[allow(unused_variables)] // cfg
516    fn validate(&self, data: &[u8], signature: &[u8], ip: Option<IpAddr>) -> Result<(), ()> {
517        match *self {
518            #[cfg(feature = "rsa")]
519            ComputedAlgo::RSASha256 { public_key, .. } => {
520                let mut hasher = sha2::Sha256::new();
521                hasher.update(data);
522                if let Some(ip) = ip {
523                    hasher.update(IpBytes::from(ip).as_ref());
524                }
525                let hash = hasher.finalize();
526                public_key
527                    .verify(rsa::Pkcs1v15Sign::new::<sha2::Sha256>(), &hash, signature)
528                    .map_err(|_| ())
529            }
530            #[cfg(feature = "hmac")]
531            ComputedAlgo::HmacSha256 { secret, .. } => {
532                // Hmac can take a key of any length
533                let mut hmac = Hmac::<sha2::Sha256>::new_from_slice(secret).unwrap();
534                hmac.update(data);
535                if let Some(ip) = ip {
536                    hmac.update(IpBytes::from(ip).as_ref());
537                }
538                let hash = hmac.finalize().into_bytes();
539                if &*hash == signature {
540                    Ok(())
541                } else {
542                    Err(())
543                }
544            }
545            #[cfg(feature = "ecdsa")]
546            ComputedAlgo::EcdsaP256 { public_key, .. } => {
547                let sig = p256::ecdsa::Signature::from_der(signature).map_err(|_| ())?;
548                if let Some(ip) = ip {
549                    let mut buf = Vec::with_capacity(data.len() + 16);
550                    buf.extend_from_slice(data);
551                    buf.extend_from_slice(IpBytes::from(ip).as_ref());
552                    public_key.verify(&buf, &sig).map_err(|_| ())
553                } else {
554                    public_key.verify(data, &sig).map_err(|_| ())
555                }
556            }
557        }
558    }
559}
560impl Validate for Mode {
561    fn validate(&self, data: &[u8], signature: &[u8], ip: Option<IpAddr>) -> Result<(), ()> {
562        (&self).validate(data, signature, ip)
563    }
564}
565impl<'a> Validate for &'a Mode {
566    fn validate(&self, data: &[u8], signature: &[u8], ip: Option<IpAddr>) -> Result<(), ()> {
567        match *self {
568            Mode::Sign(s) => s.validate(data, signature, ip),
569            #[cfg(any(feature = "rsa", feature = "ecdsa"))]
570            Mode::Validate(v) => v.validate(data, signature, ip),
571        }
572    }
573}
574#[cfg(all(test, feature = "ecdsa"))]
575impl<'a> Validate for &'a [u8] {
576    fn validate(&self, data: &[u8], signature: &[u8], ip: Option<IpAddr>) -> Result<(), ()> {
577        let signing = ecdsa_sk(self);
578        let public_key = signing.verifying_key();
579        let sig = p256::ecdsa::Signature::from_der(signature).map_err(|_| ())?;
580        if let Some(ip) = ip {
581            let mut buf = Vec::with_capacity(data.len() + 16);
582            buf.extend_from_slice(data);
583            buf.extend_from_slice(IpBytes::from(ip).as_ref());
584            public_key.verify(&buf, &sig).map_err(|_| ())
585        } else {
586            public_key.verify(data, &sig).map_err(|_| ())
587        }
588    }
589}
590#[cfg(all(test, feature = "ecdsa"))]
591impl<'a, const LEN: usize> Validate for &'a [u8; LEN] {
592    fn validate(&self, data: &[u8], signature: &[u8], ip: Option<IpAddr>) -> Result<(), ()> {
593        (&self[..]).validate(data, signature, ip)
594    }
595}
596
597macro_rules! or_unauthorized {
598    ($v: expr) => {
599        if let Some(v) = $v {
600            v
601        } else {
602            return Self::Unauthorized;
603        }
604    };
605}
606/// Returns [`None`] if `s` is not a valid JWT for `secret` and the current time.
607#[cfg(feature = "structured")]
608fn validate(s: &str, validate: impl Validate, ip: Option<IpAddr>) -> Option<serde_json::Value> {
609    let parts = s.splitn(3, '.').collect::<Vec<_>>();
610    if parts.len() != 3 {
611        return None;
612    }
613    let signature_input = &s[..parts[0].len() + 1 + parts[1].len()];
614    let remote_signature = BASE64_ENGINE.decode(parts[2]).ok()?;
615    if validate
616        .validate(signature_input.as_bytes(), &remote_signature, ip)
617        .is_err()
618    {
619        return None;
620    }
621    let payload = BASE64_ENGINE
622        .decode(parts[1])
623        .ok()
624        .and_then(|p| String::from_utf8(p).ok())?;
625    let mut payload_value: serde_json::Value = payload.parse().ok()?;
626    let payload = payload_value.as_object_mut()?;
627    let exp = payload.get("exp").and_then(|v| v.as_u64())?;
628    let iat = payload.get("iat").and_then(|v| v.as_u64())?;
629    let now = seconds_since_epoch();
630    if exp < now || iat > now {
631        return None;
632    }
633    Some(payload_value)
634}
635/// Returns [`None`] if `s` is not a valid JWT for `secret` and the current time.
636#[cfg(not(feature = "structured"))]
637fn validate(s: &str, validate: impl Validate, ip: Option<IpAddr>) -> Option<JwtData> {
638    let parts = s.splitn(3, '.').collect::<Vec<_>>();
639    if parts.len() != 3 {
640        return None;
641    }
642    let signature_input = &s[..parts[0].len() + 1 + parts[1].len()];
643    let remote_signature = BASE64_ENGINE.decode(parts[2]).ok()?;
644    if validate
645        .validate(signature_input.as_bytes(), &remote_signature, ip)
646        .is_err()
647    {
648        return None;
649    }
650    let payload = BASE64_ENGINE
651        .decode(parts[1])
652        .ok()
653        .and_then(|p| String::from_utf8(p).ok())?;
654    let mut entries = payload.strip_prefix('{')?.strip_suffix('}')?.trim();
655    let mut data = JwtData::default();
656    let mut last_missed_comma = false;
657    loop {
658        entries = if let Some(s) = entries.strip_prefix(',') {
659            s
660        } else {
661            if last_missed_comma {
662                break;
663            }
664            last_missed_comma = true;
665            entries
666        };
667        entries = entries.strip_prefix('"')?;
668        let (key, value) = unescape::unescape_until_quote(entries).and_then(|(name, pos)| {
669            // +1 for the quote
670            entries = entries[pos + 1..].trim_start();
671            entries = entries.strip_prefix(',')?.trim_start();
672            entries = entries.strip_prefix('"')?.trim_start();
673            unescape::unescape_until_quote(entries).map(|(value, pos)| {
674                entries = &entries[pos + 1..];
675                (name, value)
676            })
677        })?;
678        match key.as_str() {
679            "iat" => data.iat = value.parse().ok()?,
680            "exp" => data.exp = value.parse().ok()?,
681            "num" => data.num = Some(value.parse().ok()?),
682            "text" => data.text = Some(value),
683            _ => log::warn!("Tried to parse JWT with unrecognized field: {key:?}"),
684        }
685    }
686    let now = seconds_since_epoch();
687    if (data.exp as u64) < now || (data.iat as u64) > now {
688        return None;
689    }
690    Some(data)
691}
692#[derive(Debug, Default)]
693struct JwtData {
694    pub iat: f64,
695    pub exp: f64,
696    pub num: Option<f64>,
697    pub text: Option<String>,
698}
699#[cfg(feature = "structured")]
700impl<T: Serialize + DeserializeOwned> Validation<T> {
701    #[allow(clippy::match_result_ok)] // macro
702    fn from_jwt(s: &str, validator: impl Validate, ip: Option<IpAddr>) -> Self {
703        let mut payload = or_unauthorized!(validate(s, validator, ip));
704        let payload = payload
705            .as_object_mut()
706            .expect("we just did this conversion in the function above");
707        let variant = or_unauthorized!(payload.get("__variant").and_then(|v| v.as_str()));
708        let data = match variant {
709            "t" => {
710                let s = or_unauthorized!(payload.get("text").and_then(|v| v.as_str()));
711                AuthData::Text(s.to_owned())
712            }
713            "n" => {
714                let n = or_unauthorized!(payload.get("num").and_then(|v| v.as_f64()));
715                AuthData::Number(n)
716            }
717            "tn" => {
718                let s = or_unauthorized!(payload.get("text").and_then(|v| v.as_str()));
719                let n = or_unauthorized!(payload.get("num").and_then(|v| v.as_f64()));
720                AuthData::TextNumber(s.to_owned(), n)
721            }
722            "s" => {
723                let serialize_v = payload.get("__deserialize_v").map_or(false, |v| v == true);
724                let v = if serialize_v {
725                    or_unauthorized!(payload.get_mut("v")).take()
726                } else {
727                    payload.remove("iat");
728                    payload.remove("exp");
729                    payload.remove("__variant");
730                    payload.remove("__deserialize_v");
731                    serde_json::Value::Object(std::mem::take(payload))
732                };
733                AuthData::Structured(or_unauthorized!(serde_json::from_value(v).ok()))
734            }
735            "e" => AuthData::None,
736            _ => return Self::Unauthorized,
737        };
738        Self::Authorized(data)
739    }
740}
741#[cfg(not(feature = "structured"))]
742impl<T: Serialize + DeserializeOwned> Validation<T> {
743    #[allow(clippy::match_result_ok)] // macro
744    fn from_jwt(s: &str, validator: impl Validate, ip: Option<IpAddr>) -> Self {
745        let data = or_unauthorized!(validate(s, validator, ip));
746        let data = match (data.num, data.text) {
747            (Some(num), Some(text)) => AuthData::TextNumber(text, num),
748            (Some(num), None) => AuthData::Number(num),
749            (None, Some(text)) => AuthData::Text(text),
750            (None, None) => AuthData::None,
751        };
752        Self::Authorized(data)
753    }
754}
755
756#[derive(Debug)]
757struct CredentialsStore<'a> {
758    pub username: &'a str,
759    pub password: &'a str,
760}
761impl<'a> CredentialsStore<'a> {
762    pub fn new(username: impl Into<&'a str>, password: impl Into<&'a str>) -> Self {
763        Self {
764            username: username.into(),
765            password: password.into(),
766        }
767    }
768    pub fn to_bytes(&self, ip: Option<IpAddr>) -> Vec<u8> {
769        let mut v = Vec::with_capacity(
770            1 + ip.map_or(0, |ip| if ip.is_ipv4() { 4 } else { 16 })
771                + 8
772                + self.username.len()
773                + self.password.len(),
774        );
775        if let Some(ip) = ip {
776            let ident = if ip.is_ipv4() { 0x1 } else { 0x2 };
777            v.push(ident);
778            v.extend_from_slice(IpBytes::from(ip).as_ref());
779        } else {
780            v.push(0)
781        }
782        let len = (self.username.len() as u64).to_le_bytes();
783        v.extend_from_slice(&len);
784        v.extend_from_slice(self.username.as_bytes());
785        v.extend_from_slice(self.password.as_bytes());
786        v
787    }
788    pub fn from_bytes(mut b: &'a [u8]) -> Result<(Self, Option<&'a [u8]>), ()> {
789        (|| {
790            let mut take_n = |n: usize| {
791                let v = b.get(..n)?;
792                b = &b[n..];
793                Some(v)
794            };
795            let ip_type = take_n(1)?;
796            let ip = match ip_type[0] {
797                0x0 => None,
798                0x1 => Some(take_n(4)?),
799                0x2 => Some(take_n(16)?),
800                _ => return None,
801            };
802            let len = take_n(8)?;
803            let mut array = [0; 8];
804            array.copy_from_slice(len);
805            let len = u64::from_le_bytes(array);
806            let username = std::str::from_utf8(take_n(len as usize)?).ok()?;
807            let password = std::str::from_utf8(b).ok()?;
808            Some((Self { username, password }, ip))
809        })()
810        .ok_or(())
811    }
812}
813
814/// The algorithm used when running in validation mode.
815///
816/// `hmac` isn't available, as that doesn't use asymmetric cryptography.
817#[derive(Debug)]
818#[cfg(any(feature = "rsa", feature = "ecdsa"))]
819pub enum ValidationAlgo {
820    /// Validate RSA-signed JWTs.
821    #[cfg(feature = "rsa")]
822    RSASha256 {
823        /// The RSA public key.
824        public_key: RsaPublicKey,
825    },
826    /// Validate ecdsa-signed JWTs.
827    #[cfg(feature = "ecdsa")]
828    EcdsaP256 {
829        /// The ecdsa public key.
830        public_key: p256::ecdsa::VerifyingKey,
831    },
832}
833#[derive(Debug)]
834enum ComputedAlgo {
835    #[cfg(feature = "hmac")]
836    HmacSha256 {
837        secret: Vec<u8>,
838        credentials_key: chacha20::cipher::Key<chacha20::ChaCha12>,
839    },
840    #[cfg(feature = "rsa")]
841    RSASha256 {
842        private_key: Box<rsa::RsaPrivateKey>,
843        public_key: Box<RsaPublicKey>,
844    },
845    #[cfg(feature = "ecdsa")]
846    EcdsaP256 {
847        private_key: p256::ecdsa::SigningKey,
848        public_key: p256::ecdsa::VerifyingKey,
849        credentials_key: chacha20::cipher::Key<chacha20::ChaCha12>,
850    },
851}
852impl ComputedAlgo {
853    fn encrypt(&self, b: &[u8]) -> Vec<u8> {
854        match self {
855            #[cfg(feature = "rsa")]
856            Self::RSASha256 {
857                private_key: _,
858                public_key,
859            } => public_key
860                .encrypt(&mut rand::thread_rng(), rsa::Pkcs1v15Encrypt, b)
861                .expect("failed to encrypt with RSA"),
862            #[cfg(feature = "hmac")]
863            Self::HmacSha256 {
864                credentials_key, ..
865            } => {
866                let mut nonce = [0_u8; 12];
867                rand::thread_rng().fill(&mut nonce);
868                let mut cipher = chacha20::ChaCha12::new(credentials_key, &nonce.into());
869                let mut vec = Vec::with_capacity(12 + b.len());
870                vec.extend_from_slice(&nonce);
871                vec.extend_from_slice(b);
872                cipher.apply_keystream(&mut vec[12..]);
873                vec
874            }
875            #[cfg(feature = "ecdsa")]
876            Self::EcdsaP256 {
877                credentials_key, ..
878            } => {
879                let mut nonce = [0_u8; 12];
880                rand::thread_rng().fill(&mut nonce);
881                let mut cipher = chacha20::ChaCha12::new(credentials_key, &nonce.into());
882                let mut vec = Vec::with_capacity(12 + b.len());
883                vec.extend_from_slice(&nonce);
884                vec.extend_from_slice(b);
885                cipher.apply_keystream(&mut vec[12..]);
886                vec
887            }
888        }
889    }
890    #[allow(clippy::match_same_arms)] // cfg
891    fn decrypt<'a>(&self, b: &'a mut [u8]) -> Option<Cow<'a, [u8]>> {
892        match self {
893            #[cfg(feature = "rsa")]
894            Self::RSASha256 {
895                private_key,
896                public_key: _,
897            } => private_key
898                .decrypt(rsa::Pkcs1v15Encrypt, b)
899                .map(Cow::Owned)
900                .ok(),
901            #[cfg(feature = "hmac")]
902            Self::HmacSha256 {
903                credentials_key, ..
904            } => {
905                let mut nonce = [0_u8; 12];
906                nonce.copy_from_slice(b.get(..12)?);
907                let mut cipher = chacha20::ChaCha12::new(credentials_key, &nonce.into());
908                cipher.apply_keystream(&mut b[12..]);
909                Some(Cow::Borrowed(&b[12..]))
910            }
911
912            #[cfg(feature = "ecdsa")]
913            Self::EcdsaP256 {
914                credentials_key, ..
915            } => {
916                let mut nonce = [0_u8; 12];
917                nonce.copy_from_slice(b.get(..12)?);
918                let mut cipher = chacha20::ChaCha12::new(credentials_key, &nonce.into());
919                cipher.apply_keystream(&mut b[12..]);
920                Some(Cow::Borrowed(&b[12..]))
921            }
922        }
923    }
924}
925impl From<CryptoAlgo> for ComputedAlgo {
926    fn from(alg: CryptoAlgo) -> Self {
927        match alg {
928            #[cfg(feature = "hmac")]
929            CryptoAlgo::HmacSha256 { secret } => Self::HmacSha256 {
930                credentials_key: {
931                    let mut hasher = sha2::Sha256::new();
932                    hasher.update(&secret);
933                    hasher.finalize()
934                },
935                secret,
936            },
937            #[cfg(feature = "rsa")]
938            CryptoAlgo::RSASha256 { private_key } => Self::RSASha256 {
939                public_key: Box::new(RsaPublicKey::from(&private_key)),
940                private_key: Box::new(private_key),
941            },
942            #[cfg(feature = "ecdsa")]
943            CryptoAlgo::EcdsaP256 { secret } => {
944                let mut hasher = sha2::Sha256::new();
945                hasher.update(&secret);
946                let hash = hasher.finalize();
947                let private_key = p256::ecdsa::SigningKey::from_bytes(&hash)
948                    .expect("failed to construct a Ecdsa key");
949                Self::EcdsaP256 {
950                    public_key: *private_key.verifying_key(),
951                    private_key,
952                    credentials_key: hash,
953                }
954            }
955        }
956    }
957}
958#[derive(Debug)]
959#[allow(clippy::large_enum_variant)] // this is just the user-facing algo selector, it quickly gets
960// converted to a smaller enum
961/// The cryptographic algorithm to use to ensure the authenticity of the data.
962///
963/// I recommend `ecdsa`, as it's the fastest and has support for validation mode.
964/// `hmac` is the most common algorithm used on the web right now, so it could be useful for
965/// compatibility.
966pub enum CryptoAlgo {
967    /// Sign using Hmac.
968    #[cfg(feature = "hmac")]
969    HmacSha256 {
970        /// The Hmac secret to sign with.
971        secret: Vec<u8>,
972    },
973    /// Sign using RSA.
974    #[cfg(feature = "rsa")]
975    RSASha256 {
976        /// The RSA public key to sign with.
977        private_key: rsa::RsaPrivateKey,
978    },
979    #[cfg(feature = "ecdsa")]
980    /// Sign using Ecdsa.
981    ///
982    /// This is the recommended algo, as it allows verification without the secret (see
983    /// [`ecdsa_sk`] for more details on how to share the verification key) (RSA can also do this), is 1000x faster than
984    /// RSA, and takes up 70% less space than RSA. It's also takes any byte array as a secret.
985    EcdsaP256 {
986        /// The Ecdsa secret to sign with.
987        ///
988        /// Does currently not correspond to PKCS#8 certificates.
989        /// This can be anything you'd like.
990        secret: Vec<u8>,
991    },
992}
993/// Get the signing key for `secret`.
994///
995/// # Sharing verifying key
996///
997/// Get the verifying key by using the `verifying_key` method on the returned value.
998/// You can then use the methods [`to_encoded_point`](https://docs.rs/ecdsa/0.14.1/ecdsa/struct.VerifyingKey.html#method.to_encoded_point)
999/// and [`from_encoded_point`](https://docs.rs/ecdsa/0.14.1/ecdsa/struct.VerifyingKey.html#method.from_encoded_point)
1000/// (or any similar methods, like the Serialize serde implementation of the struct)
1001/// to serialize and share the verifying key, and then constructing [`ValidationAlgo::EcdsaP256`]
1002/// with that key.
1003#[cfg(feature = "ecdsa")]
1004pub fn ecdsa_sk(secret: &[u8]) -> p256::ecdsa::SigningKey {
1005    let mut hasher = sha2::Sha256::new();
1006    hasher.update(secret);
1007    let hash = hasher.finalize();
1008    p256::ecdsa::SigningKey::from_bytes(&hash).expect("failed to construct a Ecdsa key")
1009}
1010#[derive(Debug, Clone)]
1011enum Mode {
1012    Sign(Arc<ComputedAlgo>),
1013    #[cfg(any(feature = "rsa", feature = "ecdsa"))]
1014    Validate(Arc<ValidationAlgo>),
1015}
1016/// You can use multiple authentication setups on a single site, but make sure that the respective
1017/// [`Builder::with_cookie_path`]s do not overlap. You MUST set `with_cookie_path` to use more than
1018/// 1 auth setup.
1019#[derive(Debug, Default)]
1020pub struct Builder {
1021    auth_page_name: Option<String>,
1022    jwt_page_name_extension: String,
1023    samesite_strict: Option<bool>,
1024    httponly: Option<bool>,
1025    relogin_on_ip_change: Option<bool>,
1026    jwt_cookie_name: Option<String>,
1027    credentials_cookie_name: Option<String>,
1028    show_auth_page_when_unauthorized: Option<String>,
1029    jwt_cookie_validity: Option<Duration>,
1030    credentials_cookie_validity: Option<Duration>,
1031    cookie_path: Option<String>,
1032    read_x_real_ip_header: Option<bool>,
1033}
1034impl Builder {
1035    /// Create a new builder.
1036    /// Use [`Self::build`] or [`Self::build_validate`] to get a [`Config`].
1037    pub fn new() -> Self {
1038        Self::default()
1039    }
1040    /// Sets the URL endpoint where your frontend authenticates to.
1041    pub fn with_auth_page_name(mut self, auth_page_name: impl Into<String>) -> Self {
1042        let s = auth_page_name.into();
1043        let jwt_page_name_extension = s.replace(
1044            |c: char| {
1045                u8::try_from(c as u32).map_or(true, |b| {
1046                    !kvarn::prelude::utils::is_valid_header_value_byte(b)
1047                })
1048            },
1049            "-",
1050        );
1051        self.jwt_page_name_extension = jwt_page_name_extension;
1052        self.auth_page_name = Some(s);
1053        self
1054    }
1055    /// Decrease security and protection against CSRF but allow users to follow links to
1056    /// auth-protected pages from other sites.
1057    /// This sets the `SameSite` property of the cookie to `lax`.
1058    pub fn with_lax_samesite(mut self) -> Self {
1059        self.samesite_strict = Some(false);
1060        self
1061    }
1062    /// Decrease security and protection against XSS but allow the JavaScript to read the cookie,
1063    /// which allows the client to get the logged in status.
1064    /// **It's highly recommended to enable [`Builder::with_force_relog_on_ip_change`] when this is
1065    /// enabled, as that negates any credential theft, as the credentials are bound to an IP.**
1066    ///
1067    /// This disables the usual setting of the `HttpOnly` cookie property.
1068    /// This does not affect the credentials cookie. That will never be served without `HttpOnly`.
1069    pub fn with_relaxed_httponly(mut self) -> Self {
1070        self.httponly = Some(false);
1071        self
1072    }
1073    /// Forces relogging by the user when they change IPs. This can protect users from getting
1074    /// their cookies scraped by malware, as the authentication is IP dependant.
1075    pub fn with_force_relog_on_ip_change(mut self) -> Self {
1076        self.samesite_strict = Some(false);
1077        self
1078    }
1079    /// Sets the name of the JWT cookie. This is the cookie that authorizes the user.
1080    ///
1081    /// # Panics
1082    ///
1083    /// Panics if `jwt_cookie_name` contains illegal bytes for a header value.
1084    pub fn with_jwt_cookie_name(mut self, jwt_cookie_name: impl Into<String>) -> Self {
1085        let s = jwt_cookie_name.into();
1086        if !s
1087            .bytes()
1088            .all(kvarn::prelude::utils::is_valid_header_value_byte)
1089        {
1090            panic!("jwt_cookie_name contains illegal bytes")
1091        }
1092        self.jwt_cookie_name = Some(s);
1093        self
1094    }
1095    /// Sets the name of the credentials cookie. This is the cookie that stores the user's
1096    /// credentials to allow renewal of the JWT cookie without requiring the user to input
1097    /// credentials. It is encrypted with the server's PK.
1098    ///
1099    /// # Panics
1100    ///
1101    /// Panics if `credentials_cookie_name` contains illegal bytes for a header value.
1102    pub fn with_credentials_cookie_name(
1103        mut self,
1104        credentials_cookie_name: impl Into<String>,
1105    ) -> Self {
1106        let s = credentials_cookie_name.into();
1107        if !s
1108            .bytes()
1109            .all(kvarn::prelude::utils::is_valid_header_value_byte)
1110        {
1111            panic!("jwt_cookie_name contains illegal bytes")
1112        }
1113        self.credentials_cookie_name = Some(s);
1114        self
1115    }
1116    /// Sets the path of all the cookies. Set this to avoid slowing down other pages on your
1117    /// server, as Kvarn will try to renew the JWT on every page by default.
1118    /// By setting this to only your protected pages, the JWT cookie is only sent there.
1119    /// Kvarn thinks the user isn't logged in on other pages, reducing the work it has to do.
1120    ///
1121    /// This is also useful if you want to have multiple authentication systems on a single host.
1122    ///
1123    /// # Panics
1124    ///
1125    /// Panics if `cookie_path` contains illegal bytes for a header value.
1126    pub fn with_cookie_path(mut self, cookie_path: impl Into<String>) -> Self {
1127        let s = cookie_path.into();
1128        if !s
1129            .bytes()
1130            .all(kvarn::prelude::utils::is_valid_header_value_byte)
1131        {
1132            panic!("cookie_path contains illegal bytes")
1133        }
1134        self.cookie_path = Some(s);
1135        self
1136    }
1137    /// Show this page when the user isn't logged in.
1138    ///
1139    /// This guarantees nobody can view any pages which starts with [`Self::with_cookie_path`]
1140    /// without being logged in.
1141    ///
1142    /// Please also specify [`Self::with_cookie_path`], as else `auth_page` will be shown instead
1143    /// of every other page when not logged in.
1144    ///
1145    /// # Panics
1146    ///
1147    /// Panics if `show_auth_page_when_unauthorized` cannot be converted into a [`kvarn::prelude::HeaderValue`].
1148    /// [`kvarn::prelude::Uri`].
1149    pub fn with_show_auth_page_when_unauthorized(mut self, auth_page: impl Into<String>) -> Self {
1150        let s = auth_page.into();
1151        if kvarn::prelude::Uri::try_from(&s).is_err() {
1152            panic!("show_auth_page_when_unauthorized contains illegal bytes")
1153        }
1154        self.show_auth_page_when_unauthorized = Some(s);
1155        self
1156    }
1157    /// Makes all JWTs valid for the duration of `valid_for`.
1158    /// After that, the JWT is automatically refreshed from the securely stored credentials.
1159    pub fn with_jwt_validity(mut self, valid_for: Duration) -> Self {
1160        self.jwt_cookie_validity = Some(valid_for);
1161        self
1162    }
1163    /// Makes the credentials cookie valid for the duration of `valid_for`.
1164    /// If this is a year, the user doesn't have to relog in a year.
1165    pub fn with_credentials_cookie_validity(mut self, valid_for: Duration) -> Self {
1166        self.credentials_cookie_validity = Some(valid_for);
1167        self
1168    }
1169    /// Reads the IP from the header `x-real-ip` instead of the connection IP.
1170    /// This is useful if the authentication is behind a reverse proxy.
1171    pub fn with_ip_from_header(mut self) -> Self {
1172        self.read_x_real_ip_header = Some(true);
1173        self
1174    }
1175
1176    fn _build<
1177        T: Serialize + DeserializeOwned + Send + Sync,
1178        F: Fn(&str, &str, SocketAddr, &kvarn::FatRequest) -> Fut + Send + Sync,
1179        Fut: Future<Output = Validation<T>> + Send + Sync,
1180    >(
1181        self,
1182        is_allowed: F,
1183        mode: Mode,
1184    ) -> Arc<Config<T, F, Fut>> {
1185        let httponly = self.httponly.unwrap_or(true);
1186        let relogin_on_ip_change = self.relogin_on_ip_change.unwrap_or(false);
1187        if !httponly && !relogin_on_ip_change {
1188            log::warn!(
1189                "HttpOnly not set and relogin_on_ip_change not set. \
1190                In case of XSS attacks, the session token could be leaked"
1191            );
1192        }
1193        let c = Config {
1194            mode,
1195            is_allowed: Arc::new(is_allowed),
1196            jwt_page_name_extension: self.jwt_page_name_extension,
1197            auth_page_name: self.auth_page_name.unwrap_or_else(|| "/auth".into()),
1198            samesite_strict: self.samesite_strict.unwrap_or(true),
1199            httponly,
1200            relogin_on_ip_change,
1201            jwt_cookie_name: self.jwt_cookie_name.unwrap_or_else(|| "auth-jwt".into()),
1202            credentials_cookie_name: self
1203                .credentials_cookie_name
1204                .unwrap_or_else(|| "auth-credentials".into()),
1205            show_auth_page_when_unauthorized: self.show_auth_page_when_unauthorized,
1206            jwt_validity: self
1207                .jwt_cookie_validity
1208                .unwrap_or_else(|| Duration::from_secs(60 * 60)),
1209            credentials_cookie_validity: self
1210                .credentials_cookie_validity
1211                .unwrap_or_else(|| Duration::from_secs(60 * 60 * 24 * 365)),
1212            cookie_path: self.cookie_path.unwrap_or_else(|| String::from("/")),
1213            read_x_real_ip_header: self.read_x_real_ip_header.unwrap_or(false),
1214        };
1215        Arc::new(c)
1216    }
1217    /// Build these settings into a [`Config`], which you then use for validation.
1218    pub fn build<
1219        T: Serialize + DeserializeOwned + Send + Sync,
1220        F: Fn(&str, &str, SocketAddr, &kvarn::FatRequest) -> Fut + Send + Sync,
1221        Fut: Future<Output = Validation<T>> + Send + Sync,
1222    >(
1223        self,
1224        is_allowed: F,
1225        pk: CryptoAlgo,
1226    ) -> Arc<Config<T, F, Fut>> {
1227        self._build(is_allowed, Mode::Sign(Arc::new(pk.into())))
1228    }
1229    /// Build these settings into a [`Config`] built for validation.
1230    /// See the [module-level documentation](self) for more info.
1231    #[allow(clippy::type_complexity)]
1232    #[cfg(any(feature = "rsa", feature = "ecdsa"))]
1233    pub fn build_validate(
1234        self,
1235        validation_key: ValidationAlgo,
1236    ) -> Arc<
1237        Config<
1238            (),
1239            fn(&str, &str, SocketAddr, &kvarn::FatRequest) -> core::future::Pending<Validation<()>>,
1240            core::future::Pending<Validation<()>>,
1241        >,
1242    > {
1243        fn _placeholder(
1244            _user: &str,
1245            _password: &str,
1246            _addr: SocketAddr,
1247            _req: &kvarn::FatRequest,
1248        ) -> core::future::Pending<Validation<()>> {
1249            core::future::pending()
1250        }
1251        let placeholder: fn(
1252            &str,
1253            &str,
1254            SocketAddr,
1255            &kvarn::FatRequest,
1256        ) -> core::future::Pending<Validation<()>> = _placeholder;
1257        self._build(placeholder, Mode::Validate(Arc::new(validation_key)))
1258    }
1259}
1260/// The type of [`Config::login_status`]. Use this in the type bounds of Kvarn's extensions.
1261pub type LoginStatusClosure<T> = Arc<
1262    dyn Fn(&kvarn::FatRequest, kvarn::prelude::SocketAddr) -> Validation<T> + Send + Sync + 'static,
1263>;
1264/// The configured authentication. This can be attached to a Kvarn host using the [`Self::mount`]
1265/// method. You can call [`Self::login_status`] to get a function to use in your extensions to
1266/// check for authentication status.
1267pub struct Config<
1268    T: Serialize + DeserializeOwned + Send + Sync,
1269    F: Fn(&str, &str, SocketAddr, &kvarn::FatRequest) -> Fut + Send + Sync,
1270    Fut: Future<Output = Validation<T>> + Send + Sync,
1271> {
1272    mode: Mode,
1273    is_allowed: Arc<F>,
1274    auth_page_name: String,
1275    jwt_page_name_extension: String,
1276    samesite_strict: bool,
1277    httponly: bool,
1278    relogin_on_ip_change: bool,
1279    jwt_cookie_name: String,
1280    credentials_cookie_name: String,
1281    show_auth_page_when_unauthorized: Option<String>,
1282    jwt_validity: Duration,
1283    credentials_cookie_validity: Duration,
1284    cookie_path: String,
1285    read_x_real_ip_header: bool,
1286}
1287impl<
1288        T: Serialize + DeserializeOwned + Send + Sync + 'static,
1289        F: Fn(&str, &str, SocketAddr, &kvarn::FatRequest) -> Fut + Send + Sync + 'static,
1290        Fut: Future<Output = Validation<T>> + Send + Sync + 'static,
1291    > Config<T, F, Fut>
1292{
1293    fn ip(&self, ip: IpAddr) -> Option<IpAddr> {
1294        if self.relogin_on_ip_change {
1295            Some(ip)
1296        } else {
1297            None
1298        }
1299    }
1300    /// Returns a closure that can be sent to a Kvarn extension to extract the data from the user's
1301    /// JWT and validating the authenticity of the client.
1302    ///
1303    /// This makes it easier to cross the boundary of the Kvarn extension (using the `move ||`
1304    /// semantics). See [`LoginStatusClosure`].
1305    ///
1306    /// If the closure returns [`Validation::Unauthorized`], redirect the user to your login page
1307    /// ([`Builder::with_auth_page_name`]).
1308    pub fn login_status(&self) -> LoginStatusClosure<T> {
1309        let jwt_cookie_name = self.jwt_cookie_name.clone();
1310        let mode = self.mode.clone();
1311        let relogin_on_ip_change = self.relogin_on_ip_change;
1312        Arc::new(
1313            move |req: &kvarn::FatRequest, addr: kvarn::prelude::SocketAddr| {
1314                let ip = if relogin_on_ip_change {
1315                    Some(addr.ip())
1316                } else {
1317                    None
1318                };
1319                let cookie = get_cookie(req, &jwt_cookie_name);
1320                let cookie = if let Some(d) = cookie {
1321                    extract_cookie_value(d)
1322                } else {
1323                    return Validation::Unauthorized;
1324                };
1325                Validation::from_jwt(cookie, &mode, ip)
1326            },
1327        )
1328    }
1329    /// Create an API route at [`Builder::with_auth_page_name`] and make the JWT token automatically
1330    /// refresh.
1331    ///
1332    /// To log in, use JavaScript's `fetch` with method POST or PUT to the `auth_page_name`,
1333    /// with the username length on the first lines, then on the second line,
1334    /// the username concatenated with the password without any space.
1335    /// The rest of the body (after username length) is considered to be the password (it can
1336    /// contains newlines).
1337    ///
1338    /// To log out, `fetch` DELETE to `auth_page_name`.
1339    ///
1340    /// # Panics
1341    ///
1342    /// Panics if this config was created using [`Builder::build_validate`].
1343    #[allow(clippy::match_result_ok)]
1344    pub fn mount(self: &Arc<Self>, extensions: &mut kvarn::Extensions) {
1345        use kvarn::prelude::*;
1346
1347        #[derive(Debug, PartialEq, Eq)]
1348        enum AuthState {
1349            Authorized,
1350            /// Only the JWT cookie is definitely invalid. We will try to refresh the JWT
1351            Incorrect,
1352            /// Both the JWT cookie and the credentials cookie are invalid
1353            Missing,
1354        }
1355        type JwtCreation = Arc<
1356            dyn Fn(
1357                    &str,
1358                    &str,
1359                    SocketAddr,
1360                    &FatRequest,
1361                ) -> extensions::RetSyncFut<'static, Option<(String, String)>>
1362                + Send
1363                + Sync
1364                + 'static,
1365        >;
1366
1367        let signing_algo = match &self.mode {
1368            Mode::Sign(s) => Arc::clone(s),
1369            #[cfg(any(feature = "rsa", feature = "ecdsa"))]
1370            Mode::Validate(_v) => panic!("Called mount on a config acting as a validator."),
1371        };
1372
1373        let jwt_refresh_page =
1374            format!("/./jwt-auth-refresh-token/{}", self.jwt_page_name_extension);
1375
1376        let config = self.clone();
1377        let show_auth_page_when_unauthorized = config.show_auth_page_when_unauthorized.clone();
1378        let auth_page_name = config.auth_page_name.clone();
1379        let cookie_path = config.cookie_path.clone();
1380        let prime_signing_algo = signing_algo.clone();
1381        let validate = move |req: &FatRequest, addr: SocketAddr| {
1382            let jwt_cookie = get_cookie(req, &config.jwt_cookie_name);
1383            let credentials_cookie = get_cookie(req, &config.credentials_cookie_name);
1384            match (jwt_cookie, credentials_cookie) {
1385                (None, None) => AuthState::Missing,
1386                (None, _) => AuthState::Incorrect,
1387                (Some(jwt), _) => {
1388                    let value = extract_cookie_value(jwt);
1389                    let validation = validate(value, &*prime_signing_algo, config.ip(addr.ip()));
1390                    if validation.is_some() {
1391                        AuthState::Authorized
1392                    } else {
1393                        AuthState::Incorrect
1394                    }
1395                }
1396            }
1397        };
1398        type Validate = Arc<dyn Fn(&FatRequest, SocketAddr) -> AuthState + Send + Sync>;
1399        let validate: Validate = Arc::new(validate);
1400        let prime_jwt_refresh_page = Uri::try_from(&jwt_refresh_page)
1401            .expect("we converted all non-header safe values to hyphens");
1402        let x_real_ip = self.read_x_real_ip_header;
1403        fn check_addr(
1404            req: &FatRequest,
1405            addr: SocketAddr,
1406            x_real_ip: bool,
1407        ) -> Result<SocketAddr, ()> {
1408            if x_real_ip {
1409                if let Some(addr) = req
1410                    .headers()
1411                    .get("x-real-ip")
1412                    .and_then(|header| header.to_str().ok())
1413                    .and_then(|header| header.parse::<IpAddr>().ok())
1414                    .map(|ip| SocketAddr::new(ip, 0))
1415                {
1416                    Ok(addr)
1417                } else {
1418                    Err(())
1419                }
1420            } else {
1421                Ok(addr)
1422            }
1423        }
1424        async fn resolve_addr(
1425            req: &FatRequest,
1426            addr: SocketAddr,
1427            x_real_ip: bool,
1428            host: &Host,
1429        ) -> Result<SocketAddr, FatResponse> {
1430            if let Ok(addr) = check_addr(req, addr, x_real_ip) {
1431                Ok(addr)
1432            } else {
1433                error!(
1434                    "URGENT: `x-real-ip` header wasn't found or not valid. \
1435                    Malicious users could spoof it."
1436                );
1437                Err(default_error_response(
1438                    StatusCode::BAD_REQUEST,
1439                    host,
1440                    Some(
1441                        "the authentication extected to be behind \
1442                        a reverse proxy and to get the `x-real-ip` header.",
1443                    ),
1444                )
1445                .await)
1446            }
1447        }
1448
1449        if show_auth_page_when_unauthorized.is_some() {
1450            let cookie_path = cookie_path.clone();
1451            let validate = Arc::clone(&validate);
1452            extensions.add_package(
1453                package!(
1454                    response,
1455                    req,
1456                    _host,
1457                    addr,
1458                    move |cookie_path: String, validate: Validate, x_real_ip: bool| {
1459                        let addr = match check_addr(req, addr, *x_real_ip) {
1460                            Ok(a) => a,
1461                            Err(()) => {
1462                                // same as Incorrect
1463                                response
1464                                    .headers_mut()
1465                                    .insert("client-cache", HeaderValue::from_static("no-cache"));
1466                                return;
1467                            }
1468                        };
1469                        if req.uri().path().starts_with(cookie_path) {
1470                            let state: AuthState = validate(req, addr);
1471                            match state {
1472                                AuthState::Missing | AuthState::Incorrect => {
1473                                    response.headers_mut().insert(
1474                                        "client-cache",
1475                                        HeaderValue::from_static("no-cache"),
1476                                    );
1477                                }
1478                                AuthState::Authorized => {}
1479                            }
1480                        }
1481                    }
1482                ),
1483                Id::new(-7, "don't cache authentication website on client").no_override(),
1484            )
1485        }
1486
1487        extensions.add_prime(
1488            prime!(
1489                req,
1490                _host,
1491                addr,
1492                move |validate: Validate,
1493                      show_auth_page_when_unauthorized: Option<String>,
1494                      auth_page_name: String,
1495                      cookie_path: String,
1496                      prime_jwt_refresh_page: Uri,
1497                      x_real_ip: bool| {
1498                    if !req.uri().path().starts_with(cookie_path)
1499                        || req.uri().path() == auth_page_name
1500                    {
1501                        return None;
1502                    }
1503                    let addr = match check_addr(req, addr, *x_real_ip) {
1504                        Ok(a) => a,
1505                        // same as Incorrect
1506                        Err(()) => return Some(prime_jwt_refresh_page.clone()),
1507                    };
1508                    let state: AuthState = validate(req, addr);
1509                    match state {
1510                        AuthState::Authorized => None,
1511                        AuthState::Missing
1512                            if req.uri().path().starts_with(cookie_path)
1513                                && req.uri().path() != auth_page_name =>
1514                        {
1515                            show_auth_page_when_unauthorized.as_ref().map(|path| {
1516                                let uri = req.uri();
1517                                {
1518                                    let scheme = uri.scheme().map_or("", uri::Scheme::as_str);
1519                                    let authority =
1520                                        uri.authority().map_or("", uri::Authority::as_str);
1521                                    let bytes = build_bytes!(
1522                                        scheme.as_bytes(),
1523                                        if uri.scheme().is_some() {
1524                                            &b"://"[..]
1525                                        } else {
1526                                            &[]
1527                                        },
1528                                        authority.as_bytes(),
1529                                        path.as_bytes()
1530                                    );
1531                                    Uri::from_maybe_shared(bytes)
1532                                        .expect("invalid bytes in auth path")
1533                                }
1534                            })
1535                        }
1536                        AuthState::Missing => None,
1537                        AuthState::Incorrect => Some(prime_jwt_refresh_page.clone()),
1538                    }
1539                }
1540            ),
1541            // the priority has to be below the priority of `http_to_https_redirect`,
1542            // else the user might not be using HTTPS!
1543            extensions::Id::new(8432, "auth JWT renewal").no_override(),
1544        );
1545        let refresh_signing_algo = signing_algo.clone();
1546        // `/./jwt-auth-refresh-token` to read credentials token and write jwt token (remove
1547        // credentials if invalid), then 303 to the current page
1548        let credentials_cookie_name = self.credentials_cookie_name.clone();
1549        let jwt_cookie_name = self.jwt_cookie_name.clone();
1550        let config = self.clone();
1551        let jwt_signing_algo = signing_algo.clone();
1552        let jwt_from_credentials: JwtCreation = Arc::new(
1553            move |username: &str, password: &str, addr: SocketAddr, req: &FatRequest| {
1554                let signing_algo = jwt_signing_algo.clone();
1555                let config = config.clone();
1556                let addr = match check_addr(req, addr, x_real_ip) {
1557                    Ok(a) => a,
1558                    // same as Incorrect
1559                    Err(()) => return Box::pin(async { None }),
1560                };
1561                let fut = (config.is_allowed)(username, password, addr, req).then(
1562                    move |data| async move {
1563                        match data {
1564                            Validation::Unauthorized => None,
1565                            Validation::Authorized(data) => {
1566                                let jwt = data.into_jwt_with_default_header(
1567                                    &signing_algo,
1568                                    config.jwt_validity.as_secs(),
1569                                    config.ip(addr.ip()),
1570                                );
1571                                let header_value = format!(
1572                                    "{}={}; Secure{}; SameSite={}; Max-Age={}; Path={}",
1573                                    config.jwt_cookie_name,
1574                                    jwt,
1575                                    if config.httponly { "; HttpOnly" } else { "" },
1576                                    if config.samesite_strict {
1577                                        "Strict"
1578                                    } else {
1579                                        "Lax"
1580                                    },
1581                                    config.jwt_validity.as_secs(),
1582                                    config.cookie_path,
1583                                );
1584                                Some((header_value, jwt))
1585                            }
1586                        }
1587                    },
1588                );
1589                Box::pin(fut)
1590            },
1591        );
1592
1593        let auth_jwt_from_credentials = Arc::clone(&jwt_from_credentials);
1594        let cookie_path = self.cookie_path.clone();
1595        let prepare_extension = prepare!(
1596            req,
1597            host,
1598            _,
1599            addr,
1600            move |credentials_cookie_name: String,
1601                  jwt_cookie_name: String,
1602                  cookie_path: String,
1603                  refresh_signing_algo: Arc<ComputedAlgo>,
1604                  jwt_from_credentials: JwtCreation| {
1605                macro_rules! some_or_remove_cookie {
1606                    ($e: expr) => {
1607                        if let Some(v) = $e {
1608                            v
1609                        } else {
1610                            let do_remove_credentials = get_cookie(req, credentials_cookie_name)
1611                                .map(extract_cookie_value)
1612                                .map_or(false, |v| !v.is_empty());
1613                            let do_remove_jwt = get_cookie(req, jwt_cookie_name)
1614                                .map(extract_cookie_value)
1615                                .map_or(false, |v| !v.is_empty());
1616                            let encoding = req.headers_mut().remove("accept-encoding");
1617                            req.headers_mut()
1618                                .insert("accept-encoding", HeaderValue::from_static("identity"));
1619
1620                            remove_cookie(req, credentials_cookie_name);
1621                            remove_cookie(req, jwt_cookie_name);
1622
1623                            let mut response = kvarn::handle_cache(req, addr, host).await;
1624
1625                            if do_remove_credentials {
1626                                remove_set_cookie(
1627                                    &mut response.response,
1628                                    credentials_cookie_name,
1629                                    cookie_path,
1630                                );
1631                            }
1632                            if do_remove_jwt {
1633                                remove_set_cookie(
1634                                    &mut response.response,
1635                                    jwt_cookie_name,
1636                                    cookie_path,
1637                                );
1638                            }
1639                            if let Some(encoding) = encoding {
1640                                req.headers_mut().insert("accept-encoding", encoding);
1641                            }
1642
1643                            let mut fat_response = FatResponse::no_cache(response.response);
1644                            if let Some(f) = response.future {
1645                                fat_response = fat_response.with_future_and_maybe_len(f);
1646                            }
1647                            return fat_response;
1648                        }
1649                    };
1650                }
1651
1652                let req: &mut FatRequest = req;
1653
1654                let addr = match resolve_addr(req, addr, x_real_ip, host).await {
1655                    Ok(a) => a,
1656                    Err(resp) => return resp,
1657                };
1658
1659                if let Some(header) = req.headers().get("x-kvarn-auth-processed") {
1660                    error!(
1661                        "This request has been processed by another auth instance or ourselves. \
1662                        If you are certain you specified different \
1663                        `cookie_path`s in the builder, please report this bug. \
1664                        If this message occurs more than once, it's a serious recursion bug."
1665                    );
1666                    if header == "true" {
1667                        req.headers_mut()
1668                            .insert("x-kvarn-auth-processed", HeaderValue::from_static("error"));
1669                        // try to get actual response
1670                        remove_cookie(req, credentials_cookie_name);
1671                        remove_cookie(req, jwt_cookie_name);
1672
1673                        let response = kvarn::handle_cache(req, addr, host).await;
1674                        let mut fat_response = FatResponse::no_cache(response.response);
1675                        if let Some(f) = response.future {
1676                            fat_response = fat_response.with_future_and_maybe_len(f);
1677                        }
1678                        return fat_response;
1679                    } else {
1680                        // don't recursively call handle_cache, which could lead to this codepath
1681                        // again
1682                        return default_error_response(
1683                            StatusCode::INTERNAL_SERVER_ERROR,
1684                            host,
1685                            Some("auth experienced an internal error"),
1686                        )
1687                        .await;
1688                    }
1689                }
1690                req.headers_mut()
1691                    .insert("x-kvarn-auth-processed", HeaderValue::from_static("true"));
1692
1693                let credentials_cookie = get_cookie(req, credentials_cookie_name);
1694                let credentials =
1695                    some_or_remove_cookie!(credentials_cookie.map(extract_cookie_value));
1696                let mut rsa_credentials = Vec::new();
1697                some_or_remove_cookie!(BASE64_ENGINE
1698                    .decode_vec(credentials, &mut rsa_credentials)
1699                    .ok());
1700                let decrypted =
1701                    some_or_remove_cookie!(refresh_signing_algo.decrypt(&mut rsa_credentials));
1702                let (credentials, credentials_ip) =
1703                    some_or_remove_cookie!(CredentialsStore::from_bytes(&decrypted).ok());
1704
1705                if let Some(ip) = credentials_ip {
1706                    // the IP addresses doesn't match
1707                    if ip != IpBytes::from(addr.ip()).as_ref() {
1708                        some_or_remove_cookie!(None);
1709                    }
1710                }
1711
1712                let jwt =
1713                    jwt_from_credentials(credentials.username, credentials.password, addr, req)
1714                        .await;
1715                let (jwt, jwt_value) = some_or_remove_cookie!(jwt);
1716
1717                if let Some((cookie, pos, header_pos)) =
1718                    get_cookie_with_header_pos(req, jwt_cookie_name)
1719                {
1720                    let new_cookie_header =
1721                        cookie.replace(extract_cookie_value((cookie, pos)), &jwt_value);
1722                    let header_to_change = req.headers_mut().entry("cookie");
1723                    if let header::Entry::Occupied(mut entry) = header_to_change {
1724                        let header_to_change = entry.iter_mut().nth(header_pos).unwrap();
1725                        *header_to_change = HeaderValue::from_str(&new_cookie_header)
1726                            .expect("JWT refresh contains illegal bytes in the header");
1727                    } else {
1728                        unreachable!(
1729                            "The header must be present, \
1730                            since we got the data from it in the previous call"
1731                        );
1732                    }
1733                } else if let Some(h) = req.headers_mut().get_mut("cookie") {
1734                    let mut new = BytesMut::with_capacity(
1735                        h.as_bytes().len() + 2 + jwt_cookie_name.len() + 1 + jwt_value.len(),
1736                    );
1737                    new.extend_from_slice(h.as_bytes());
1738                    new.extend_from_slice(b"; ");
1739                    new.extend_from_slice(jwt_cookie_name.as_bytes());
1740                    new.extend_from_slice(b"=");
1741                    new.extend_from_slice(jwt_value.as_bytes());
1742                    *h = HeaderValue::from_maybe_shared(new)
1743                        .expect("JWT refresh contains illegal bytes in the header");
1744                } else {
1745                    let mut new =
1746                        BytesMut::with_capacity(jwt_cookie_name.len() + 1 + jwt_value.len());
1747                    new.extend_from_slice(jwt_cookie_name.as_bytes());
1748                    new.extend_from_slice(b"=");
1749                    new.extend_from_slice(jwt_value.as_bytes());
1750                    req.headers_mut().insert(
1751                        "cookie",
1752                        HeaderValue::from_maybe_shared(new)
1753                            .expect("JWT refresh contains illegal bytes in the header"),
1754                    );
1755                }
1756
1757                let encoding = req.headers_mut().remove("accept-encoding");
1758                req.headers_mut()
1759                    .insert("accept-encoding", HeaderValue::from_static("identity"));
1760
1761                let mut response = kvarn::handle_cache(req, addr, host).await;
1762                response.response.headers_mut().append(
1763                    "set-cookie",
1764                    HeaderValue::from_str(&jwt)
1765                        .expect("JWT refresh contains illegal bytes in the header"),
1766                );
1767
1768                if let Some(encoding) = encoding {
1769                    req.headers_mut().insert("accept-encoding", encoding);
1770                }
1771
1772                let mut fat_response = FatResponse::no_cache(response.response);
1773                if let Some(f) = response.future {
1774                    fat_response = fat_response.with_future_and_maybe_len(f);
1775                }
1776                fat_response
1777            }
1778        );
1779        extensions.add_prepare_single(jwt_refresh_page, prepare_extension);
1780
1781        // `/<auth-page-name>` to accept POST & PUT methods and the return a jwt and credentials
1782        // token. (use same jwt function as the other page)
1783        let config = self.clone();
1784        let new_credentials_cookie = Box::new(move |contents: &str| {
1785            format!(
1786                "{}={}; Secure; HttpOnly; SameSite={}; Max-Age={}; Path={}",
1787                config.credentials_cookie_name,
1788                contents,
1789                if config.samesite_strict {
1790                    "Strict"
1791                } else {
1792                    "Lax"
1793                },
1794                config.credentials_cookie_validity.as_secs(),
1795                config.cookie_path,
1796            )
1797        });
1798        let config = self.clone();
1799        let relogin_on_ip_change = config.relogin_on_ip_change;
1800        let jwt_cookie_name = config.jwt_cookie_name.clone();
1801        let credentials_cookie_name = config.credentials_cookie_name.clone();
1802        let cookie_path = config.cookie_path.clone();
1803        extensions.add_prepare_single(
1804            &config.auth_page_name,
1805            prepare!(
1806                req,
1807                host,
1808                _path,
1809                addr,
1810                move |auth_jwt_from_credentials: JwtCreation,
1811                      signing_algo: Arc<ComputedAlgo>,
1812                      new_credentials_cookie: Box<dyn Fn(&str) -> String + Send + Sync>,
1813                      relogin_on_ip_change: bool,
1814                      credentials_cookie_name: String,
1815                      jwt_cookie_name: String,
1816                      cookie_path: String| {
1817                    macro_rules! some_or_return {
1818                        ($e: expr, $status: expr) => {
1819                            if let Some(v) = $e {
1820                                v
1821                            } else {
1822                                return kvarn::default_error_response($status, host, None).await;
1823                            }
1824                        };
1825                        ($e: expr, $status: expr, $message: expr) => {
1826                            if let Some(v) = $e {
1827                                v
1828                            } else {
1829                                return kvarn::default_error_response(
1830                                    $status,
1831                                    host,
1832                                    Some($message),
1833                                )
1834                                .await;
1835                            }
1836                        };
1837                    }
1838                    let addr = match resolve_addr(req, addr, x_real_ip, host).await {
1839                        Ok(a) => a,
1840                        Err(resp) => return resp,
1841                    };
1842
1843                    match *req.method() {
1844                        // continue with the normal control flow
1845                        Method::POST | Method::PUT => {}
1846                        Method::DELETE => {
1847                            let mut response = Response::new(Bytes::new());
1848                            remove_set_cookie(&mut response, jwt_cookie_name, cookie_path);
1849                            remove_set_cookie(&mut response, credentials_cookie_name, cookie_path);
1850                            return FatResponse::no_cache(response);
1851                        }
1852                        _ => {
1853                            return default_error_response(
1854                                StatusCode::METHOD_NOT_ALLOWED,
1855                                host,
1856                                Some("POST or PUT to log in, DELETE to log out"),
1857                            )
1858                            .await
1859                        }
1860                    }
1861
1862                    let body = some_or_return!(
1863                        req.body_mut().read_to_bytes(1024 * 4).await.ok(),
1864                        StatusCode::BAD_REQUEST
1865                    );
1866                    let body =
1867                        some_or_return!(std::str::from_utf8(&body).ok(), StatusCode::BAD_REQUEST);
1868                    let (username_length, credentials) = some_or_return!(
1869                        body.split_once('\n'),
1870                        StatusCode::BAD_REQUEST,
1871                        "the first line needs to be the username's length in bytes"
1872                    );
1873                    let username_length: usize = some_or_return!(
1874                        username_length.parse().ok(),
1875                        StatusCode::BAD_REQUEST,
1876                        "the first line needs to be the username's length in bytes"
1877                    );
1878                    let username = some_or_return!(
1879                        credentials.get(..username_length),
1880                        StatusCode::BAD_REQUEST,
1881                        "the username length was invalid"
1882                    );
1883                    let password = some_or_return!(
1884                        credentials.get(username_length..),
1885                        StatusCode::BAD_REQUEST,
1886                        "the username length was invalid; couldn't read password"
1887                    );
1888                    let (jwt_header, _jwt_value) = some_or_return!(
1889                        auth_jwt_from_credentials(username, password, addr, req).await,
1890                        StatusCode::UNAUTHORIZED,
1891                        "the credentials are invalid"
1892                    );
1893                    let credentials = CredentialsStore::new(username, password);
1894                    let credentials_bin = credentials.to_bytes(if *relogin_on_ip_change {
1895                        Some(addr.ip())
1896                    } else {
1897                        None
1898                    });
1899                    let encrypted = signing_algo.encrypt(&credentials_bin);
1900                    let mut credentials_header = String::new();
1901                    BASE64_ENGINE.encode_string(&encrypted, &mut credentials_header);
1902                    let credentials_header = new_credentials_cookie(&credentials_header);
1903                    FatResponse::no_cache(
1904                        Response::builder()
1905                            .header("set-cookie", jwt_header)
1906                            .header("set-cookie", credentials_header)
1907                            .body(Bytes::new())
1908                            .expect(
1909                                "JWT or credentials header contains invalid bytes for a header",
1910                            ),
1911                    )
1912                }
1913            ),
1914        );
1915    }
1916}
1917
1918#[cfg(test)]
1919mod tests {
1920    use super::*;
1921    use std::collections::HashMap;
1922
1923    #[cfg(feature = "ecdsa")]
1924    fn test_computed_algo(secret: &[u8]) -> ComputedAlgo {
1925        CryptoAlgo::EcdsaP256 {
1926            secret: secret.to_vec(),
1927        }
1928        .into()
1929    }
1930
1931    #[test]
1932    #[cfg(feature = "structured")]
1933    fn serde() {
1934        let mut map = HashMap::new();
1935        map.insert("loggedInAs".to_owned(), "admin".to_owned());
1936        let d = AuthData::Structured(map);
1937        let token = d.into_jwt_with_default_header(&test_computed_algo(b"secretkey"), 60, None);
1938
1939        let v = Validation::<HashMap<String, String>>::from_jwt(&token, b"secretkey", None);
1940        match v {
1941            Validation::Authorized(AuthData::Structured(map)) => {
1942                assert_eq!(map["loggedInAs"], "admin");
1943                assert_eq!(map.len(), 1)
1944            }
1945            Validation::Authorized(_) => panic!("wrong __variant"),
1946            Validation::Unauthorized => panic!("unauthorized"),
1947        }
1948    }
1949    #[test]
1950    #[cfg(all(feature = "ecdsa", feature = "structured"))]
1951    fn tampering_1() {
1952        let mut map = HashMap::new();
1953        map.insert("loggedInAs".to_owned(), "admin".to_owned());
1954        let d = AuthData::Structured(map);
1955        // eyJhbGciOiJIUzI1NiJ9.eyJfX3ZhcmlhbnQiOiJzIiwiZXhwIjoxNjU5NDc3MjA4LCJpYXQiOjE2NTk0NzcxNDgsImxvZ2dlZEluQXMiOiJhZG1pbiJ9.p4V5nMMHYbri-na4aEPJzVIMb2U1XhEH9RmL8Hurra4
1956        let _token = d.into_jwt_with_default_header(&test_computed_algo(b"secretkey"), 60, None);
1957
1958        // changed `loggedInAs` to `superuser`
1959        let tampered_token = "eyJhbGciOiJIUzI1NiJ9.eyJfX3ZhcmlhbnQiOiJzIiwiZXhwIjoxNjU5NDc3MjA4LCJpYXQiOjE2NTk0NzcxNDgsImxvZ2dlZEluQXMiOiJzdXBlcnVzZXIifQ.p4V5nMMHYbri-na4aEPJzVIMb2U1XhEH9RmL8Hurra4";
1960
1961        let v = Validation::<HashMap<String, String>>::from_jwt(tampered_token, b"secretkey", None);
1962        match v {
1963            Validation::Authorized(_) => panic!("should be unauthorized"),
1964            Validation::Unauthorized => {}
1965        }
1966    }
1967    #[test]
1968    #[cfg(feature = "ecdsa")]
1969    fn tampering_2() {
1970        let d = AuthData::<()>::Text("user".to_owned());
1971        let _token = d.into_jwt_with_default_header(&test_computed_algo(b"secretkey"), 60, None);
1972
1973        let d = AuthData::<()>::Text("admin".to_owned());
1974        let tampered_token =
1975            d.into_jwt_with_default_header(&test_computed_algo(b"the hacker's secret"), 60, None);
1976
1977        let v =
1978            Validation::<HashMap<String, String>>::from_jwt(&tampered_token, b"secretkey", None);
1979        match v {
1980            Validation::Authorized(_) => panic!("should be unauthorized"),
1981            Validation::Unauthorized => {}
1982        }
1983    }
1984}