autoendpoint/extractors/
authorization_check.rs1use crate::auth::sign_with_key;
2use crate::error::{ApiError, ApiErrorKind};
3use crate::headers::util::get_header;
4use crate::server::AppState;
5use actix_web::dev::Payload;
6use actix_web::{web::Data, FromRequest, HttpRequest};
7use futures::future::LocalBoxFuture;
8use futures::FutureExt;
9use openssl::error::ErrorStack;
10use uuid::Uuid;
11
12use autopush_common::util::user_agent::UserAgentInfo;
13
14pub struct AuthorizationCheck {
22 pub user_agent: UserAgentInfo,
23}
24
25impl AuthorizationCheck {
26 pub fn generate_token(auth_key: &str, user: &Uuid) -> Result<String, ErrorStack> {
27 sign_with_key(auth_key.as_bytes(), user.as_simple().to_string().as_bytes())
28 }
29
30 pub fn validate_token(
31 token: &str,
32 uaid: &Uuid,
33 auth_keys: &[String],
34 user_agent: UserAgentInfo,
35 ) -> Result<Self, ApiError> {
36 for key in auth_keys {
38 let expected_token =
39 sign_with_key(key.as_bytes(), uaid.as_simple().to_string().as_bytes())
40 .map_err(ApiErrorKind::RegistrationSecretHash)?;
41
42 debug!("expected: {:?}, recv'd {:?}", &expected_token, &token);
43 if expected_token.len() == token.len()
44 && openssl::memcmp::eq(expected_token.as_bytes(), token.as_bytes())
45 {
46 return Ok(Self { user_agent });
47 }
48 }
49 Err(ApiErrorKind::InvalidLocalAuth("incorrect auth token".to_owned()).into())
50 }
51}
52
53impl FromRequest for AuthorizationCheck {
54 type Error = ApiError;
55 type Future = LocalBoxFuture<'static, Result<Self, Self::Error>>;
56
57 fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
58 let req = req.clone();
59
60 async move {
61 let uaid = req
62 .match_info()
63 .get("uaid")
64 .expect("{uaid} must be part of the path")
65 .parse::<Uuid>()
66 .map_err(|_| ApiErrorKind::NoUser)?;
67 let state: Data<AppState> = Data::extract(&req)
68 .into_inner()
69 .expect("No server state found");
70 let auth_header = get_header(&req, "Authorization")
71 .ok_or_else(|| ApiErrorKind::InvalidLocalAuth("missing auth header".to_owned()))?;
72 let token = get_token_from_auth_header(auth_header)
73 .ok_or_else(|| ApiErrorKind::InvalidLocalAuth("missing auth token".to_owned()))?;
74 let user_agent = UserAgentInfo::from(&req);
75
76 Self::validate_token(token, &uaid, &state.settings.auth_keys(), user_agent)
77 }
78 .boxed_local()
79 }
80}
81
82fn get_token_from_auth_header(header: &str) -> Option<&str> {
84 let mut split = header.splitn(2, ' ');
85 let scheme = split.next()?;
86
87 if !["bearer", "webpush"].contains(&scheme.to_lowercase().as_str()) {
90 return None;
91 }
92
93 split.next()
94}
95
96#[cfg(test)]
97mod test {
98
99 use crate::error::ApiResult;
100 use autopush_common::util::user_agent::UserAgentInfo;
101
102 use super::*;
103
104 #[test]
105 fn test_signature() -> ApiResult<()> {
106 let uaid: Uuid = "729e5104f5f04abc9196085340317dea".parse().unwrap();
108 let auth_keys = ["HJVPy4ZwF4Yz_JdvXTL8hRcwIhv742vC60Tg5Ycrvw8=".to_owned()].to_vec();
109 let token = AuthorizationCheck::generate_token(auth_keys.first().unwrap(), &uaid).unwrap();
110
111 AuthorizationCheck::validate_token(&token, &uaid, &auth_keys, UserAgentInfo::default())?;
112 Ok(())
113 }
114
115 #[test]
116 fn test_legacy_signature() -> ApiResult<()> {
117 let uaid: Uuid = "729e5104f5f04abc9196085340317dea".parse().unwrap();
120 let auth_keys = ["HJVPy4ZwF4Yz_JdvXTL8hRcwIhv742vC60Tg5Ycrvw8=".to_owned()].to_vec();
122 let legacy_token = "f694963453adf5dedcc379bbdd6900d692b6e09f1c91f44169bfcd2f941bf36c";
124 let selected = auth_keys.first().unwrap();
126 let token = AuthorizationCheck::generate_token(selected, &uaid).unwrap();
127
128 assert_eq!(&token, legacy_token);
129 Ok(())
130 }
131
132 #[test]
133 fn test_token_extractor() -> ApiResult<()> {
134 let uaid: Uuid = "729e5104f5f04abc9196085340317dea".parse().unwrap();
135 let auth_keys = ["HJVPy4ZwF4Yz_JdvXTL8hRcwIhv742vC60Tg5Ycrvw8=".to_owned()].to_vec();
136 let token = AuthorizationCheck::generate_token(auth_keys.first().unwrap(), &uaid).unwrap();
137
138 assert!(get_token_from_auth_header(&format!("bearer {}", &token)).is_some());
139 assert!(get_token_from_auth_header(&format!("webpush {}", &token)).is_some());
140 assert!(get_token_from_auth_header(&format!("random {}", &token)).is_none());
141 Ok(())
142 }
143}