Page MenuHomePhabricator

D8911.diff
No OneTemporary

D8911.diff

diff --git a/services/backup/Cargo.lock b/services/backup/Cargo.lock
--- a/services/backup/Cargo.lock
+++ b/services/backup/Cargo.lock
@@ -45,7 +45,7 @@
"actix-service",
"actix-utils",
"ahash 0.8.3",
- "base64",
+ "base64 0.21.2",
"bitflags 1.3.2",
"brotli",
"bytes",
@@ -236,6 +236,21 @@
"syn 1.0.109",
]
+[[package]]
+name = "actix-web-httpauth"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6dda62cf04bc3a9ad2ea8f314f721951cfdb4cdacec4e984d20e77c7bb170991"
+dependencies = [
+ "actix-utils",
+ "actix-web",
+ "base64 0.13.1",
+ "futures-core",
+ "futures-util",
+ "log",
+ "pin-project-lite",
+]
+
[[package]]
name = "addr2line"
version = "0.21.0"
@@ -754,6 +769,12 @@
"uuid",
]
+[[package]]
+name = "base64"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
+
[[package]]
name = "base64"
version = "0.21.2"
@@ -928,10 +949,12 @@
"actix-cors",
"actix-multipart",
"actix-web",
+ "actix-web-httpauth",
"anyhow",
"aws-config",
"aws-sdk-dynamodb",
"aws-types",
+ "base64 0.21.2",
"chrono",
"derive_more",
"futures-core",
@@ -2051,7 +2074,7 @@
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e9ad3fe7488d7e34558a2033d45a0c90b72d97b4f80705666fea71472e2e6a1"
dependencies = [
- "base64",
+ "base64 0.21.2",
"bytes",
"encoding_rs",
"futures-core",
@@ -2158,7 +2181,7 @@
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2"
dependencies = [
- "base64",
+ "base64 0.21.2",
]
[[package]]
diff --git a/services/blob/Cargo.lock b/services/blob/Cargo.lock
--- a/services/blob/Cargo.lock
+++ b/services/blob/Cargo.lock
@@ -236,6 +236,21 @@
"syn 1.0.103",
]
+[[package]]
+name = "actix-web-httpauth"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6dda62cf04bc3a9ad2ea8f314f721951cfdb4cdacec4e984d20e77c7bb170991"
+dependencies = [
+ "actix-utils",
+ "actix-web",
+ "base64 0.13.1",
+ "futures-core",
+ "futures-util",
+ "log",
+ "pin-project-lite",
+]
+
[[package]]
name = "adler"
version = "1.0.2"
@@ -978,15 +993,19 @@
"actix-cors",
"actix-multipart",
"actix-web",
+ "actix-web-httpauth",
"anyhow",
"aws-config",
"aws-sdk-dynamodb",
"aws-types",
+ "base64 0.21.0",
"chrono",
"derive_more",
"futures-core",
"futures-util",
"http",
+ "serde",
+ "serde_json",
"tokio-stream",
"tracing",
]
diff --git a/services/comm-services-lib/Cargo.lock b/services/comm-services-lib/Cargo.lock
--- a/services/comm-services-lib/Cargo.lock
+++ b/services/comm-services-lib/Cargo.lock
@@ -45,7 +45,7 @@
"actix-service",
"actix-utils",
"ahash 0.8.3",
- "base64",
+ "base64 0.21.0",
"bitflags",
"brotli",
"bytes",
@@ -236,6 +236,21 @@
"syn 1.0.109",
]
+[[package]]
+name = "actix-web-httpauth"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6dda62cf04bc3a9ad2ea8f314f721951cfdb4cdacec4e984d20e77c7bb170991"
+dependencies = [
+ "actix-utils",
+ "actix-web",
+ "base64 0.13.1",
+ "futures-core",
+ "futures-util",
+ "log",
+ "pin-project-lite",
+]
+
[[package]]
name = "addr2line"
version = "0.21.0"
@@ -652,6 +667,12 @@
"rustc-demangle",
]
+[[package]]
+name = "base64"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
+
[[package]]
name = "base64"
version = "0.21.0"
@@ -782,10 +803,12 @@
"actix-cors",
"actix-multipart",
"actix-web",
+ "actix-web-httpauth",
"anyhow",
"aws-config",
"aws-sdk-dynamodb",
"aws-types",
+ "base64 0.21.0",
"chrono",
"derive_more",
"futures-core",
@@ -1758,7 +1781,7 @@
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cde824a14b7c14f85caff81225f411faacc04a2013f41670f41443742b1c1c55"
dependencies = [
- "base64",
+ "base64 0.21.0",
"bytes",
"encoding_rs",
"futures-core",
@@ -1866,7 +1889,7 @@
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b"
dependencies = [
- "base64",
+ "base64 0.21.0",
]
[[package]]
diff --git a/services/comm-services-lib/Cargo.toml b/services/comm-services-lib/Cargo.toml
--- a/services/comm-services-lib/Cargo.toml
+++ b/services/comm-services-lib/Cargo.toml
@@ -9,8 +9,6 @@
"dep:reqwest",
"dep:futures-core",
"dep:futures-util",
- "dep:serde",
- "dep:serde_json",
"dep:tokio",
]
http = [
@@ -21,12 +19,16 @@
"dep:futures-util",
"dep:http",
"dep:tokio-stream",
+ "dep:actix-web-httpauth",
]
[dependencies]
+serde = { version = "1.0", features = ["derive"] }
+serde_json = "1.0"
aws-config = "0.55"
aws-sdk-dynamodb = "0.27"
aws-types = "0.55"
+base64 = "0.21"
chrono = "0.4"
derive_more = "0.99"
tracing = "0.1"
@@ -39,12 +41,11 @@
"multipart",
"stream",
], optional = true }
-serde = { version = "1.0", features = ["derive"], optional = true }
-serde_json = { version = "1.0", optional = true }
tokio = { version = "1.32", optional = true }
# http dependencies
actix-cors = { version = "0.6", optional = true }
actix-web = { version = "4.3", optional = true }
http = { version = "0.2.9", optional = true }
+actix-web-httpauth = { version = "0.8.0", optional = true }
actix-multipart = { version = "0.6", optional = true }
tokio-stream = { version = "0.1.14", optional = true }
diff --git a/services/comm-services-lib/src/auth.rs b/services/comm-services-lib/src/auth.rs
new file mode 100644
--- /dev/null
+++ b/services/comm-services-lib/src/auth.rs
@@ -0,0 +1,79 @@
+use base64::{prelude::BASE64_STANDARD, Engine};
+use derive_more::{Display, Error, From};
+use serde::{Deserialize, Serialize};
+use std::{str::FromStr, string::FromUtf8Error};
+
+/// This implements [`actix_web::FromRequest`], so it can be used to extract user
+/// identity information from HTTP requests.
+/// # Example
+/// ```ignore
+/// pub async fn request_handler(
+/// user: UserIdentity,
+/// ) -> Result<HttpResponse> {
+/// Ok(HttpResponse::Ok().finish())
+/// }
+/// ```
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
+pub struct UserIdentity {
+ #[serde(rename = "userID")]
+ pub user_id: String,
+ #[serde(rename = "accessToken")]
+ pub access_token: String,
+ #[serde(rename = "deviceID")]
+ pub device_id: String,
+}
+
+impl UserIdentity {
+ /// Gets the access token value, usable in bearer authorization
+ ///
+ /// # Example
+ /// ```ignore
+ /// reqwest::get("url").beaerer_auth(user.as_authorization_token()?).send().await?;
+ /// ```
+ pub fn as_authorization_token(&self) -> Result<String, serde_json::Error> {
+ let json = serde_json::to_string(self)?;
+ let base64_str = BASE64_STANDARD.encode(json);
+ Ok(base64_str)
+ }
+}
+
+#[derive(Debug, Display, Error, From)]
+pub enum UserIdentityParseError {
+ Base64DecodeError(base64::DecodeError),
+ Utf8DecodeError(FromUtf8Error),
+ JsonParseError(serde_json::Error),
+}
+
+/// Parsing of [UserIdentity] from bearer token
+impl FromStr for UserIdentity {
+ type Err = UserIdentityParseError;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ let bytes = BASE64_STANDARD.decode(s)?;
+ let text = String::from_utf8(bytes)?;
+ let user = serde_json::from_str(&text)?;
+ Ok(user)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_user_identity_parsing() {
+ let identity = UserIdentity {
+ user_id: "user".to_string(),
+ access_token: "token".to_string(),
+ device_id: "device".to_string(),
+ };
+ let json =
+ r#"{"userID": "user", "accessToken": "token", "deviceID": "device"}"#;
+ let encoded = BASE64_STANDARD.encode(json);
+
+ let parsed_identity = encoded.parse::<UserIdentity>();
+ assert!(parsed_identity.is_ok(), "Parse error: {parsed_identity:?}");
+
+ assert_eq!(parsed_identity.unwrap(), identity);
+ }
+}
diff --git a/services/comm-services-lib/src/http.rs b/services/comm-services-lib/src/http.rs
--- a/services/comm-services-lib/src/http.rs
+++ b/services/comm-services-lib/src/http.rs
@@ -1,3 +1,4 @@
+pub mod auth;
pub mod multipart;
use crate::tools::BoxedError;
diff --git a/services/comm-services-lib/src/http/auth.rs b/services/comm-services-lib/src/http/auth.rs
new file mode 100644
--- /dev/null
+++ b/services/comm-services-lib/src/http/auth.rs
@@ -0,0 +1,108 @@
+use actix_web::{
+ body::{EitherBody, MessageBody},
+ dev::{Service, ServiceRequest, ServiceResponse, Transform},
+ FromRequest, HttpMessage,
+};
+use actix_web_httpauth::{
+ extractors::{bearer::BearerAuth, AuthenticationError},
+ headers::www_authenticate::bearer::Bearer,
+ middleware::HttpAuthentication,
+};
+use std::{
+ future::{ready, Ready},
+ str::FromStr,
+};
+use tracing::debug;
+
+use crate::auth::UserIdentity;
+
+impl FromRequest for UserIdentity {
+ type Error = actix_web::Error;
+ type Future = Ready<Result<Self, Self::Error>>;
+
+ fn from_request(
+ req: &actix_web::HttpRequest,
+ _: &mut actix_web::dev::Payload,
+ ) -> Self::Future {
+ if let Some(user) = req.extensions().get::<UserIdentity>() {
+ return ready(Ok(user.clone()));
+ }
+
+ let f = || {
+ let bearer = BearerAuth::extract(req).into_inner()?;
+ let user = match UserIdentity::from_str(bearer.token()) {
+ Ok(user) => user,
+ Err(err) => {
+ debug!("HTTP authorization error: {err}");
+ return Err(AuthenticationError::new(Bearer::default()).into());
+ }
+ };
+
+ Ok(user)
+ };
+
+ ready(f())
+ }
+}
+
+pub async fn validation_function(
+ req: ServiceRequest,
+ bearer: BearerAuth,
+) -> Result<ServiceRequest, (actix_web::Error, ServiceRequest)> {
+ let user = match UserIdentity::from_str(bearer.token()) {
+ Ok(user) => user,
+ Err(err) => {
+ debug!("HTTP authorization error: {err}");
+ return Err((AuthenticationError::new(Bearer::default()).into(), req));
+ }
+ };
+
+ // TODO: call identity service, for now just allow every request
+ req.extensions_mut().insert(user);
+
+ Ok(req)
+}
+
+/// Use this to add Authentication Middleware. It's going to parse Authorization
+/// header and call the identity service to check if the provided credentials
+/// are correct. If not it's going to reject the request.
+///
+/// # Example
+/// ```ignore
+/// let auth_middleware = get_comm_authentication_middleware();
+/// App::new().wrap(auth_middleware);
+/// ```
+/// If you don't want all of the routes to require authentication you can wrap
+/// individual resources or scopes:
+/// ```ignore
+/// App::new().service(
+/// web::resource("/endpoint").route(web::get().to(handler)).wrap(auth_middleware),
+/// )
+/// ```
+// This type is very complicated, but unfortunately typing this directly
+// requires https://github.com/rust-lang/rust/issues/99697 to be merged.
+// The issue is that we can't specify the second generic argument of
+// HttpAuthentication<T, F>, because it look something like this:
+// ```
+// impl Fn(ServiceRequest, BearerAuth) -> impl Future<
+// Output = Result<ServiceRequest, (actix_web::Error, ServiceRequest)>,
+// >
+// ``
+// which isn't valid (until the linked issue is merged).
+pub fn get_comm_authentication_middleware<B, S>() -> impl Transform<
+ S,
+ ServiceRequest,
+ Response = ServiceResponse<EitherBody<B>>,
+ Error = actix_web::Error,
+ InitError = (),
+> + 'static
+where
+ B: MessageBody + 'static,
+ S: Service<
+ ServiceRequest,
+ Response = ServiceResponse<B>,
+ Error = actix_web::Error,
+ > + 'static,
+{
+ HttpAuthentication::bearer(validation_function)
+}
diff --git a/services/comm-services-lib/src/lib.rs b/services/comm-services-lib/src/lib.rs
--- a/services/comm-services-lib/src/lib.rs
+++ b/services/comm-services-lib/src/lib.rs
@@ -1,3 +1,4 @@
+pub mod auth;
pub mod blob;
pub mod constants;
pub mod database;
diff --git a/services/feature-flags/Cargo.lock b/services/feature-flags/Cargo.lock
--- a/services/feature-flags/Cargo.lock
+++ b/services/feature-flags/Cargo.lock
@@ -731,8 +731,11 @@
"aws-config",
"aws-sdk-dynamodb",
"aws-types",
+ "base64",
"chrono",
"derive_more",
+ "serde",
+ "serde_json",
"tracing",
]

File Metadata

Mime Type
text/plain
Expires
Fri, Nov 22, 10:03 AM (9 h, 45 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2561172
Default Alt Text
D8911.diff (12 KB)

Event Timeline