auth.rs (23954B)
1 use crate::{ 2 config::{self, Config}, 3 error::Error, 4 }; 5 use chrono::{TimeDelta, Utc}; 6 use jsonwebtoken::{self, errors::ErrorKind, Algorithm, DecodingKey, EncodingKey, Header}; 7 use openssl::pkey::{Id, PKey}; 8 use serde::de::DeserializeOwned; 9 use serde::ser::Serialize; 10 use std::fs::File; 11 use std::io::{Read, Write}; 12 use std::sync::OnceLock; 13 14 static DEFAULT_VALIDITY: OnceLock<TimeDelta> = OnceLock::new(); 15 #[inline] 16 fn init_default_validity() { 17 DEFAULT_VALIDITY 18 .set(TimeDelta::try_hours(2).expect("TimeDelta::try_hours(2) should work")) 19 .expect("DEFAULT_VALIDITY must only be initialized once"); 20 } 21 #[inline] 22 pub fn get_default_validity() -> &'static TimeDelta { 23 DEFAULT_VALIDITY 24 .get() 25 .expect("DEFAULT_VALIDITY must be initialized in main") 26 } 27 static JWT_HEADER: OnceLock<Header> = OnceLock::new(); 28 #[inline] 29 fn init_jwt_header() { 30 JWT_HEADER 31 .set(Header::new(JWT_ALGORITHM)) 32 .expect("JWT_HEADER must only be initialized once"); 33 } 34 #[inline] 35 fn get_jwt_header() -> &'static Header { 36 JWT_HEADER 37 .get() 38 .expect("JWT_HEADER must be initialized in main") 39 } 40 static JWT_LOGIN_ISSUER: OnceLock<String> = OnceLock::new(); 41 #[inline] 42 fn init_jwt_login_issuer() { 43 JWT_LOGIN_ISSUER 44 .set(format!("{}|login", config::get_config().domain_origin())) 45 .expect("JWT_LOGIN_ISSUER must only be initialized once"); 46 } 47 #[inline] 48 pub fn get_jwt_login_issuer() -> &'static str { 49 JWT_LOGIN_ISSUER 50 .get() 51 .expect("JWT_LOGIN_ISSUER must be initialized in main") 52 .as_str() 53 } 54 static JWT_INVITE_ISSUER: OnceLock<String> = OnceLock::new(); 55 #[inline] 56 fn init_jwt_invite_issuer() { 57 JWT_INVITE_ISSUER 58 .set(format!("{}|invite", config::get_config().domain_origin())) 59 .expect("JWT_INVITE_ISSUER must only be initialized once"); 60 } 61 #[inline] 62 fn get_jwt_invite_issuer() -> &'static str { 63 JWT_INVITE_ISSUER 64 .get() 65 .expect("JWT_INVITE_ISSUER must be initialized in main") 66 .as_str() 67 } 68 static JWT_DELETE_ISSUER: OnceLock<String> = OnceLock::new(); 69 #[inline] 70 fn init_jwt_delete_issuer() { 71 JWT_DELETE_ISSUER 72 .set(format!("{}|delete", config::get_config().domain_origin())) 73 .expect("JWT_DELETE_ISSUER must only be initialized once"); 74 } 75 #[inline] 76 fn get_jwt_delete_issuer() -> &'static str { 77 JWT_DELETE_ISSUER 78 .get() 79 .expect("JWT_DELETE_ISSUER must be initialized in main") 80 .as_str() 81 } 82 const JWT_ALGORITHM: Algorithm = Algorithm::EdDSA; 83 static ED_KEYS: OnceLock<(EncodingKey, DecodingKey)> = OnceLock::new(); 84 #[allow(clippy::map_err_ignore, clippy::verbose_file_reads)] 85 #[inline] 86 fn init_ed_keys() -> Result<(), Error> { 87 let mut file = File::options() 88 .create(true) 89 .read(true) 90 .truncate(false) 91 .write(true) 92 .open(Config::PRIVATE_ED25519_KEY)?; 93 let mut priv_pem = Vec::with_capacity(128); 94 let ed_key = if file.read_to_end(&mut priv_pem)? == 0 { 95 let ed_key = PKey::generate_ed25519()?; 96 priv_pem = ed_key.private_key_to_pem_pkcs8()?; 97 file.write_all(priv_pem.as_slice())?; 98 ed_key 99 } else { 100 let ed_key = PKey::private_key_from_pem(priv_pem.as_slice())?; 101 if ed_key.id() == Id::ED25519 { 102 ed_key 103 } else { 104 let msg = format!( 105 "{} is not a private Ed25519 key", 106 Config::PRIVATE_ED25519_KEY 107 ); 108 return Err(Error::new(msg.as_str(), msg.as_str())); 109 } 110 }; 111 ED_KEYS 112 .set(( 113 EncodingKey::from_ed_pem(priv_pem.as_slice())?, 114 DecodingKey::from_ed_pem(ed_key.public_key_to_pem()?.as_slice())?, 115 )) 116 .map_err(|_| { 117 const MSG: &str = "ED_KEYS must only be initialized once"; 118 Error::new(MSG, MSG) 119 }) 120 } 121 #[inline] 122 fn get_private_ed_key() -> &'static EncodingKey { 123 &ED_KEYS 124 .get() 125 .expect("ED_KEYS must be initialized in main") 126 .0 127 } 128 #[inline] 129 fn get_public_ed_key() -> &'static DecodingKey { 130 &ED_KEYS 131 .get() 132 .expect("ED_KEYS must be initialized in main") 133 .1 134 } 135 #[inline] 136 pub fn init_values() { 137 init_default_validity(); 138 init_jwt_header(); 139 init_jwt_login_issuer(); 140 init_jwt_invite_issuer(); 141 init_jwt_delete_issuer(); 142 init_ed_keys().expect("error creating Ed25519 keys"); 143 } 144 pub fn encode_jwt<T: Serialize>(claims: &T) -> String { 145 match jsonwebtoken::encode(get_jwt_header(), claims, get_private_ed_key()) { 146 Ok(token) => token, 147 Err(e) => panic!("Error encoding jwt {e}"), 148 } 149 } 150 151 #[allow(clippy::match_same_arms)] 152 fn decode_jwt<T: DeserializeOwned>(token: &str, issuer: String) -> Result<T, Error> { 153 let mut validation = jsonwebtoken::Validation::new(JWT_ALGORITHM); 154 validation.leeway = 30; // 30 seconds 155 validation.validate_exp = true; 156 validation.validate_nbf = true; 157 validation.set_issuer(&[issuer]); 158 let token = token.replace(char::is_whitespace, ""); 159 match jsonwebtoken::decode(&token, get_public_ed_key(), &validation) { 160 Ok(d) => Ok(d.claims), 161 Err(err) => match *err.kind() { 162 ErrorKind::InvalidToken => err!("Token is invalid"), 163 ErrorKind::InvalidIssuer => err!("Issuer is invalid"), 164 ErrorKind::ExpiredSignature => err!("Token has expired"), 165 ErrorKind::InvalidSignature 166 | ErrorKind::InvalidEcdsaKey 167 | ErrorKind::InvalidRsaKey(_) 168 | ErrorKind::RsaFailedSigning 169 | ErrorKind::InvalidAlgorithmName 170 | ErrorKind::InvalidKeyFormat 171 | ErrorKind::MissingRequiredClaim(_) 172 | ErrorKind::InvalidAudience 173 | ErrorKind::InvalidSubject 174 | ErrorKind::ImmatureSignature 175 | ErrorKind::InvalidAlgorithm 176 | ErrorKind::MissingAlgorithm 177 | ErrorKind::Base64(_) 178 | ErrorKind::Json(_) 179 | ErrorKind::Utf8(_) 180 | ErrorKind::Crypto(_) => err!("Error decoding JWT"), 181 _ => err!("Error decoding JWT"), 182 }, 183 } 184 } 185 pub fn decode_login(token: &str) -> Result<LoginJwtClaims, Error> { 186 decode_jwt(token, get_jwt_login_issuer().to_owned()) 187 } 188 pub fn decode_invite(token: &str) -> Result<InviteJwtClaims, Error> { 189 decode_jwt(token, get_jwt_invite_issuer().to_owned()) 190 } 191 pub fn decode_delete(token: &str) -> Result<BasicJwtClaims, Error> { 192 decode_jwt(token, get_jwt_delete_issuer().to_owned()) 193 } 194 195 #[derive(Serialize, Deserialize)] 196 pub struct LoginJwtClaims { 197 // Not before 198 pub nbf: i64, 199 // Expiration time 200 pub exp: i64, 201 // Issuer 202 pub iss: String, 203 // Subject 204 pub sub: String, 205 pub premium: bool, 206 pub name: String, 207 pub email: String, 208 pub email_verified: bool, 209 // --- 210 // Disabled these keys to be added to the JWT since they could cause the JWT to get too large 211 // Also These key/value pairs are not used anywhere by either Vaultwarden or Bitwarden Clients 212 // Because these might get used in the future, and they are added by the Bitwarden Server, lets keep it, but then commented out 213 // See: https://github.com/dani-garcia/vaultwarden/issues/4156 214 // --- 215 // pub orgowner: Vec<String>, 216 // pub orgadmin: Vec<String>, 217 // pub orguser: Vec<String>, 218 // pub orgmanager: Vec<String>, 219 // user security_stamp 220 pub sstamp: String, 221 // device uuid 222 pub device: String, 223 // [ "api", "offline_access" ] 224 pub scope: Vec<String>, 225 // [ "Application" ] 226 pub amr: Vec<String>, 227 } 228 229 #[derive(Serialize, Deserialize)] 230 pub struct InviteJwtClaims { 231 // Not before 232 nbf: i64, 233 // Expiration time 234 exp: i64, 235 // Issuer 236 iss: String, 237 // Subject 238 sub: String, 239 pub email: String, 240 pub org_id: Option<String>, 241 pub user_org_id: Option<String>, 242 invited_by_email: Option<String>, 243 } 244 245 #[derive(Serialize, Deserialize)] 246 pub struct FileDownloadClaims { 247 // Not before 248 nbf: i64, 249 // Expiration time 250 exp: i64, 251 // Issuer 252 iss: String, 253 // Subject 254 pub sub: String, 255 pub file_id: String, 256 } 257 #[derive(Serialize, Deserialize)] 258 pub struct BasicJwtClaims { 259 // Not before 260 nbf: i64, 261 // Expiration time 262 exp: i64, 263 // Issuer 264 iss: String, 265 // Subject 266 pub sub: String, 267 } 268 use crate::db::{ 269 models::{ 270 Collection, Device, User, UserOrgStatus, UserOrgType, UserOrganization, UserStampException, 271 }, 272 DbConn, 273 }; 274 use rocket::{ 275 outcome::try_outcome, 276 request::{FromRequest, Outcome, Request}, 277 }; 278 279 struct Host { 280 host: String, 281 } 282 283 #[rocket::async_trait] 284 impl<'r> FromRequest<'r> for Host { 285 type Error = &'static str; 286 async fn from_request(_: &'r Request<'_>) -> Outcome<Self, Self::Error> { 287 Outcome::Success(Self { 288 host: config::get_config().domain_url().to_owned(), 289 }) 290 } 291 } 292 pub struct ClientHeaders { 293 #[allow(dead_code)] 294 pub device_type: i32, 295 pub ip: ClientIp, 296 } 297 298 #[rocket::async_trait] 299 impl<'r> FromRequest<'r> for ClientHeaders { 300 type Error = &'static str; 301 async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> { 302 let Outcome::Success(ip) = ClientIp::from_request(request).await else { 303 err_handler!("Error getting Client IP") 304 }; 305 // When unknown or unable to parse, return 14, which is 'Unknown Browser' 306 let device_type: i32 = request 307 .headers() 308 .get_one("device-type") 309 .map_or(14i32, |d| d.parse().unwrap_or(14i32)); 310 311 Outcome::Success(Self { device_type, ip }) 312 } 313 } 314 315 pub struct Headers { 316 pub host: String, 317 pub device: Device, 318 pub user: User, 319 pub ip: ClientIp, 320 } 321 #[allow(clippy::else_if_without_else)] 322 #[rocket::async_trait] 323 impl<'r> FromRequest<'r> for Headers { 324 type Error = &'static str; 325 async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> { 326 let headers = request.headers(); 327 let host = try_outcome!(Host::from_request(request).await).host; 328 let Outcome::Success(ip) = ClientIp::from_request(request).await else { 329 err_handler!("Error getting Client IP") 330 }; 331 // Get access_token 332 let access_token: &str = match headers.get_one("Authorization") { 333 Some(a) => match a.rsplit("Bearer ").next() { 334 Some(split) => split, 335 None => err_handler!("No access token provided"), 336 }, 337 None => err_handler!("No access token provided"), 338 }; 339 // Check JWT token is valid and get device and user from it 340 let Ok(claims) = decode_login(access_token) else { 341 err_handler!("Invalid claim") 342 }; 343 let device_uuid = claims.device; 344 let user_uuid = claims.sub; 345 let Outcome::Success(conn) = DbConn::from_request(request).await else { 346 err_handler!("Error getting DB") 347 }; 348 let Some(device) = Device::find_by_uuid_and_user(&device_uuid, &user_uuid, &conn).await 349 else { 350 err_handler!("Invalid device id") 351 }; 352 let Some(user) = User::find_by_uuid(&user_uuid, &conn).await else { 353 err_handler!("Device has no user associated") 354 }; 355 if user.security_stamp != claims.sstamp { 356 if let Some(stamp_exception) = user 357 .stamp_exception 358 .as_deref() 359 .and_then(|s| serde_json::from_str::<UserStampException>(s).ok()) 360 { 361 let Some(current_route) = request.route().and_then(|r| r.name.as_deref()) else { 362 err_handler!("Error getting current route for stamp exception") 363 }; 364 // Check if the stamp exception has expired first. 365 // Then, check if the current route matches any of the allowed routes. 366 // After that check the stamp in exception matches the one in the claims. 367 if u64::try_from(Utc::now().naive_utc().and_utc().timestamp()).expect("underflow") 368 > stamp_exception.expire 369 { 370 // If the stamp exception has been expired remove it from the database. 371 // This prevents checking this stamp exception for new requests. 372 let mut user = user; 373 user.reset_stamp_exception(); 374 if let Err(e) = user.save(&conn).await { 375 error!("Error updating user: {:#?}", e); 376 } 377 err_handler!("Stamp exception is expired") 378 } else if !stamp_exception.routes.contains(¤t_route.to_owned()) { 379 err_handler!( 380 "Invalid security stamp: Current route and exception route do not match" 381 ) 382 } else if stamp_exception.security_stamp != claims.sstamp { 383 err_handler!("Invalid security stamp for matched stamp exception") 384 } 385 } else { 386 err_handler!("Invalid security stamp") 387 } 388 } 389 Outcome::Success(Self { 390 host, 391 device, 392 user, 393 ip, 394 }) 395 } 396 } 397 398 pub struct OrgHeaders { 399 host: String, 400 device: Device, 401 user: User, 402 org_user_type: UserOrgType, 403 org_user: UserOrganization, 404 ip: ClientIp, 405 } 406 407 #[rocket::async_trait] 408 impl<'r> FromRequest<'r> for OrgHeaders { 409 type Error = &'static str; 410 async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> { 411 let headers = try_outcome!(Headers::from_request(request).await); 412 // org_id is usually the second path param ("/organizations/<org_id>"), 413 // but there are cases where it is a query value. 414 // First check the path, if this is not a valid uuid, try the query values. 415 let url_org_id: Option<&str> = { 416 let mut url_org_id = None; 417 if let Some(Ok(org_id)) = request.param::<&str>(1) { 418 if uuid::Uuid::parse_str(org_id).is_ok() { 419 url_org_id = Some(org_id); 420 } 421 } 422 if let Some(Ok(org_id)) = request.query_value::<&str>("organizationId") { 423 if uuid::Uuid::parse_str(org_id).is_ok() { 424 url_org_id = Some(org_id); 425 } 426 } 427 url_org_id 428 }; 429 match url_org_id { 430 Some(org_id) => { 431 let user = headers.user; 432 let org_user = match DbConn::from_request(request).await { 433 Outcome::Success(conn) => { 434 match UserOrganization::find_by_user_and_org(&user.uuid, org_id, &conn) 435 .await 436 { 437 Some(user) => { 438 if user.status == i32::from(UserOrgStatus::Confirmed) { 439 user 440 } else { 441 err_handler!( 442 "The current user isn't confirmed member of the organization" 443 ) 444 } 445 } 446 None => { 447 err_handler!("The current user isn't member of the organization") 448 } 449 } 450 } 451 Outcome::Error(_) | Outcome::Forward(_) => err_handler!("Error getting DB"), 452 }; 453 Outcome::Success(Self { 454 host: headers.host, 455 device: headers.device, 456 user, 457 org_user_type: { 458 if let Ok(org_usr_type) = UserOrgType::try_from(org_user.atype) { 459 org_usr_type 460 } else { 461 // This should only happen if the DB is corrupted 462 err_handler!("Unknown user type in the database") 463 } 464 }, 465 org_user, 466 ip: headers.ip, 467 }) 468 } 469 _ => err_handler!("Error getting the organization id"), 470 } 471 } 472 } 473 474 pub struct AdminHeaders { 475 pub host: String, 476 pub device: Device, 477 pub user: User, 478 pub org_user_type: UserOrgType, 479 pub client_version: Option<String>, 480 pub ip: ClientIp, 481 } 482 483 #[rocket::async_trait] 484 impl<'r> FromRequest<'r> for AdminHeaders { 485 type Error = &'static str; 486 async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> { 487 let headers = try_outcome!(OrgHeaders::from_request(request).await); 488 let client_version = request 489 .headers() 490 .get_one("Bitwarden-Client-Version") 491 .map(String::from); 492 if headers.org_user_type >= UserOrgType::Admin { 493 Outcome::Success(Self { 494 host: headers.host, 495 device: headers.device, 496 user: headers.user, 497 org_user_type: headers.org_user_type, 498 client_version, 499 ip: headers.ip, 500 }) 501 } else { 502 err_handler!("You need to be Admin or Owner to call this endpoint") 503 } 504 } 505 } 506 507 impl From<AdminHeaders> for Headers { 508 fn from(h: AdminHeaders) -> Self { 509 Self { 510 host: h.host, 511 device: h.device, 512 user: h.user, 513 ip: h.ip, 514 } 515 } 516 } 517 518 // col_id is usually the fourth path param ("/organizations/<org_id>/collections/<col_id>"), 519 // but there could be cases where it is a query value. 520 // First check the path, if this is not a valid uuid, try the query values. 521 fn get_col_id(request: &Request<'_>) -> Option<String> { 522 if let Some(Ok(col_id)) = request.param::<String>(3) { 523 if uuid::Uuid::parse_str(&col_id).is_ok() { 524 return Some(col_id); 525 } 526 } 527 if let Some(Ok(col_id)) = request.query_value::<String>("collectionId") { 528 if uuid::Uuid::parse_str(&col_id).is_ok() { 529 return Some(col_id); 530 } 531 } 532 None 533 } 534 535 /// The ManagerHeaders are used to check if you are at least a Manager 536 /// and have access to the specific collection provided via the <col_id>/collections/collectionId. 537 /// This does strict checking on the collection_id, ManagerHeadersLoose does not. 538 pub struct ManagerHeaders { 539 host: String, 540 device: Device, 541 pub user: User, 542 ip: ClientIp, 543 } 544 545 #[rocket::async_trait] 546 impl<'r> FromRequest<'r> for ManagerHeaders { 547 type Error = &'static str; 548 async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> { 549 let headers = try_outcome!(OrgHeaders::from_request(request).await); 550 if headers.org_user_type >= UserOrgType::Manager { 551 match get_col_id(request) { 552 Some(col_id) => { 553 let Outcome::Success(conn) = DbConn::from_request(request).await else { 554 err_handler!("Error getting DB") 555 }; 556 557 if !Collection::can_access_collection(&headers.org_user, &col_id, &conn).await { 558 err_handler!("The current user isn't a manager for this collection") 559 } 560 } 561 _ => err_handler!("Error getting the collection id"), 562 } 563 564 Outcome::Success(Self { 565 host: headers.host, 566 device: headers.device, 567 user: headers.user, 568 ip: headers.ip, 569 }) 570 } else { 571 err_handler!("You need to be a Manager, Admin or Owner to call this endpoint") 572 } 573 } 574 } 575 576 impl From<ManagerHeaders> for Headers { 577 fn from(h: ManagerHeaders) -> Self { 578 Self { 579 host: h.host, 580 device: h.device, 581 user: h.user, 582 ip: h.ip, 583 } 584 } 585 } 586 587 /// The ManagerHeadersLoose is used when you at least need to be a Manager, 588 /// but there is no collection_id sent with the request (either in the path or as form data). 589 pub struct ManagerHeadersLoose { 590 host: String, 591 device: Device, 592 pub user: User, 593 pub org_user: UserOrganization, 594 ip: ClientIp, 595 } 596 597 #[rocket::async_trait] 598 impl<'r> FromRequest<'r> for ManagerHeadersLoose { 599 type Error = &'static str; 600 async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> { 601 let headers = try_outcome!(OrgHeaders::from_request(request).await); 602 if headers.org_user_type >= UserOrgType::Manager { 603 Outcome::Success(Self { 604 host: headers.host, 605 device: headers.device, 606 user: headers.user, 607 org_user: headers.org_user, 608 ip: headers.ip, 609 }) 610 } else { 611 err_handler!("You need to be a Manager, Admin or Owner to call this endpoint") 612 } 613 } 614 } 615 616 impl From<ManagerHeadersLoose> for Headers { 617 fn from(h: ManagerHeadersLoose) -> Self { 618 Self { 619 host: h.host, 620 device: h.device, 621 user: h.user, 622 ip: h.ip, 623 } 624 } 625 } 626 627 impl ManagerHeaders { 628 pub async fn from_loose( 629 h: ManagerHeadersLoose, 630 collections: &Vec<String>, 631 conn: &DbConn, 632 ) -> Result<Self, Error> { 633 for col_id in collections { 634 if uuid::Uuid::parse_str(col_id).is_err() { 635 err!("Collection Id is malformed!"); 636 } 637 if !Collection::can_access_collection(&h.org_user, col_id, conn).await { 638 err!("You don't have access to all collections!"); 639 } 640 } 641 Ok(Self { 642 host: h.host, 643 device: h.device, 644 user: h.user, 645 ip: h.ip, 646 }) 647 } 648 } 649 650 pub struct OwnerHeaders { 651 #[allow(dead_code)] 652 pub device: Device, 653 pub user: User, 654 #[allow(dead_code)] 655 pub ip: ClientIp, 656 } 657 658 #[rocket::async_trait] 659 impl<'r> FromRequest<'r> for OwnerHeaders { 660 type Error = &'static str; 661 async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> { 662 let headers = try_outcome!(OrgHeaders::from_request(request).await); 663 if headers.org_user_type == UserOrgType::Owner { 664 Outcome::Success(Self { 665 device: headers.device, 666 user: headers.user, 667 ip: headers.ip, 668 }) 669 } else { 670 err_handler!("You need to be Owner to call this endpoint") 671 } 672 } 673 } 674 675 // 676 // Client IP address detection 677 // 678 use std::net::IpAddr; 679 #[derive(Clone, Copy)] 680 pub struct ClientIp { 681 pub ip: IpAddr, 682 } 683 684 #[rocket::async_trait] 685 impl<'r> FromRequest<'r> for ClientIp { 686 type Error = (); 687 #[allow(clippy::map_err_ignore, clippy::string_slice)] 688 async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> { 689 let ip = request.headers().get_one("X-Real-IP").and_then(|ip| { 690 ip.find(',') 691 .map_or(ip, |idx| &ip[..idx]) 692 .parse() 693 .map_err(|_| warn!("'X-Real-IP' header is malformed: {ip}")) 694 .ok() 695 }); 696 let ip = ip 697 .or_else(|| request.remote().map(|r| r.ip())) 698 .unwrap_or_else(|| "0.0.0.0".parse().unwrap()); 699 Outcome::Success(Self { ip }) 700 } 701 } 702 703 pub struct WsAccessTokenHeader { 704 #[allow(dead_code)] 705 pub access_token: Option<String>, 706 } 707 708 #[rocket::async_trait] 709 impl<'r> FromRequest<'r> for WsAccessTokenHeader { 710 type Error = (); 711 async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> { 712 let headers = request.headers(); 713 let access_token = headers 714 .get_one("Authorization") 715 .and_then(|a| a.rsplit("Bearer ").next().map(String::from)); 716 Outcome::Success(Self { access_token }) 717 } 718 }