1#![doc = include_str!("../README.md")]
2#![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#[cfg(not(feature = "structured"))]
53pub trait Serialize {}
54#[cfg(not(feature = "structured"))]
55impl<T> Serialize for T {}
56#[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#[derive(Debug)]
153pub enum AuthData<T: Serialize + DeserializeOwned = ()> {
154 None,
156 Text(String),
158 Number(f64),
160 TextNumber(String, f64),
162 Structured(T),
167}
168#[cfg(feature = "hmac")]
169fn hmac_sha256(secret: &[u8], bytes: &[u8]) -> impl AsRef<[u8]> {
170 type HmacSha256 = Hmac<sha2::Sha256>;
171 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 #[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 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 #[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 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 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#[derive(Debug)]
439pub enum Validation<T: Serialize + DeserializeOwned> {
440 Unauthorized,
449 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)] 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)] 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 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#[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#[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 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)] 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)] 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#[derive(Debug)]
818#[cfg(any(feature = "rsa", feature = "ecdsa"))]
819pub enum ValidationAlgo {
820 #[cfg(feature = "rsa")]
822 RSASha256 {
823 public_key: RsaPublicKey,
825 },
826 #[cfg(feature = "ecdsa")]
828 EcdsaP256 {
829 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)] 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)] pub enum CryptoAlgo {
967 #[cfg(feature = "hmac")]
969 HmacSha256 {
970 secret: Vec<u8>,
972 },
973 #[cfg(feature = "rsa")]
975 RSASha256 {
976 private_key: rsa::RsaPrivateKey,
978 },
979 #[cfg(feature = "ecdsa")]
980 EcdsaP256 {
986 secret: Vec<u8>,
991 },
992}
993#[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#[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 pub fn new() -> Self {
1038 Self::default()
1039 }
1040 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 pub fn with_lax_samesite(mut self) -> Self {
1059 self.samesite_strict = Some(false);
1060 self
1061 }
1062 pub fn with_relaxed_httponly(mut self) -> Self {
1070 self.httponly = Some(false);
1071 self
1072 }
1073 pub fn with_force_relog_on_ip_change(mut self) -> Self {
1076 self.samesite_strict = Some(false);
1077 self
1078 }
1079 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 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 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 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 pub fn with_jwt_validity(mut self, valid_for: Duration) -> Self {
1160 self.jwt_cookie_validity = Some(valid_for);
1161 self
1162 }
1163 pub fn with_credentials_cookie_validity(mut self, valid_for: Duration) -> Self {
1166 self.credentials_cookie_validity = Some(valid_for);
1167 self
1168 }
1169 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 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 #[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}
1260pub type LoginStatusClosure<T> = Arc<
1262 dyn Fn(&kvarn::FatRequest, kvarn::prelude::SocketAddr) -> Validation<T> + Send + Sync + 'static,
1263>;
1264pub 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 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 #[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 Incorrect,
1352 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 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 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 extensions::Id::new(8432, "auth JWT renewal").no_override(),
1544 );
1545 let refresh_signing_algo = signing_algo.clone();
1546 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 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 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 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 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 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 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 let _token = d.into_jwt_with_default_header(&test_computed_algo(b"secretkey"), 60, None);
1957
1958 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}