mirror of
				https://github.com/meilisearch/meilisearch.git
				synced 2025-10-30 23:46:28 +00:00 
			
		
		
		
	feat(auth): API keys
implements: https://github.com/meilisearch/specifications/blob/develop/text/0085-api-keys.md - Add tests on API keys management route (meilisearch-http/tests/auth/api_keys.rs) - Add tests checking authorizations on each meilisearch routes (meilisearch-http/tests/auth/authorization.rs) - Implement API keys management routes (meilisearch-http/src/routes/api_key.rs) - Create module to manage API keys and authorizations (meilisearch-auth) - Reimplement GuardedData to extend authorizations (meilisearch-http/src/extractors/authentication/mod.rs) - Change X-MEILI-API-KEY by Authorization Bearer (meilisearch-http/src/extractors/authentication/mod.rs) - Change meilisearch routes to fit to the new authorization feature (meilisearch-http/src/routes/) - close #1867
This commit is contained in:
		
							
								
								
									
										1168
									
								
								meilisearch-http/tests/auth/api_keys.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1168
									
								
								meilisearch-http/tests/auth/api_keys.rs
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										412
									
								
								meilisearch-http/tests/auth/authorization.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										412
									
								
								meilisearch-http/tests/auth/authorization.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,412 @@ | ||||
| use crate::common::Server; | ||||
| use maplit::hashmap; | ||||
| use once_cell::sync::Lazy; | ||||
| use serde_json::{json, Value}; | ||||
| use std::collections::{HashMap, HashSet}; | ||||
|  | ||||
| static AUTHORIZATIONS: Lazy<HashMap<(&'static str, &'static str), &'static str>> = | ||||
|     Lazy::new(|| { | ||||
|         hashmap! { | ||||
|             ("POST",    "/indexes/products/search") =>                         "search", | ||||
|             ("GET",     "/indexes/products/search") =>                         "search", | ||||
|             ("POST",    "/indexes/products/documents") =>                      "documents.add", | ||||
|             ("GET",     "/indexes/products/documents") =>                      "documents.get", | ||||
|             ("GET",     "/indexes/products/documents/0") =>                    "documents.get", | ||||
|             ("DELETE",  "/indexes/products/documents/0") =>                    "documents.delete", | ||||
|             ("GET",     "/tasks") =>                                           "tasks.get", | ||||
|             ("GET",     "/indexes/products/tasks") =>                          "tasks.get", | ||||
|             ("GET",     "/indexes/products/tasks/0") =>                        "tasks.get", | ||||
|             ("PUT",     "/indexes/products/") =>                               "indexes.update", | ||||
|             ("GET",     "/indexes/products/") =>                               "indexes.get", | ||||
|             ("DELETE",  "/indexes/products/") =>                               "indexes.delete", | ||||
|             ("POST",    "/indexes") =>                                         "indexes.add", | ||||
|             ("GET",     "/indexes") =>                                         "indexes.get", | ||||
|             ("GET",     "/indexes/products/settings") =>                       "settings.get", | ||||
|             ("GET",     "/indexes/products/settings/displayed-attributes") =>  "settings.get", | ||||
|             ("GET",     "/indexes/products/settings/distinct-attribute") =>    "settings.get", | ||||
|             ("GET",     "/indexes/products/settings/filterable-attributes") => "settings.get", | ||||
|             ("GET",     "/indexes/products/settings/ranking-rules") =>         "settings.get", | ||||
|             ("GET",     "/indexes/products/settings/searchable-attributes") => "settings.get", | ||||
|             ("GET",     "/indexes/products/settings/sortable-attributes") =>   "settings.get", | ||||
|             ("GET",     "/indexes/products/settings/stop-words") =>            "settings.get", | ||||
|             ("GET",     "/indexes/products/settings/synonyms") =>              "settings.get", | ||||
|             ("DELETE",  "/indexes/products/settings") =>                       "settings.update", | ||||
|             ("POST",    "/indexes/products/settings") =>                       "settings.update", | ||||
|             ("POST",    "/indexes/products/settings/displayed-attributes") =>  "settings.update", | ||||
|             ("POST",    "/indexes/products/settings/distinct-attribute") =>    "settings.update", | ||||
|             ("POST",    "/indexes/products/settings/filterable-attributes") => "settings.update", | ||||
|             ("POST",    "/indexes/products/settings/ranking-rules") =>         "settings.update", | ||||
|             ("POST",    "/indexes/products/settings/searchable-attributes") => "settings.update", | ||||
|             ("POST",    "/indexes/products/settings/sortable-attributes") =>   "settings.update", | ||||
|             ("POST",    "/indexes/products/settings/stop-words") =>            "settings.update", | ||||
|             ("POST",    "/indexes/products/settings/synonyms") =>              "settings.update", | ||||
|             ("GET",     "/indexes/products/stats") =>                          "stats.get", | ||||
|             ("GET",     "/stats") =>                                           "stats.get", | ||||
|             ("POST",    "/dumps") =>                                           "dumps.create", | ||||
|             ("GET",     "/dumps/0/status") =>                                  "dumps.get", | ||||
|             ("GET",     "/version") =>                                         "version", | ||||
|         } | ||||
|     }); | ||||
|  | ||||
| static ALL_ACTIONS: Lazy<HashSet<&'static str>> = | ||||
|     Lazy::new(|| AUTHORIZATIONS.values().cloned().collect()); | ||||
|  | ||||
| static INVALID_RESPONSE: Lazy<Value> = Lazy::new(|| { | ||||
|     json!({"message": "The provided API key is invalid.", | ||||
|         "code": "invalid_api_key", | ||||
|         "type": "auth", | ||||
|         "link": "https://docs.meilisearch.com/errors#invalid_api_key" | ||||
|     }) | ||||
| }); | ||||
|  | ||||
| #[actix_rt::test] | ||||
| async fn error_access_expired_key() { | ||||
|     let mut server = Server::new_auth().await; | ||||
|     server.use_api_key("MASTER_KEY"); | ||||
|  | ||||
|     let content = json!({ | ||||
|         "indexes": ["products"], | ||||
|         "actions": ALL_ACTIONS.clone(), | ||||
|         "expiresAt": "2020-11-13T00:00:00Z" | ||||
|     }); | ||||
|  | ||||
|     let (response, code) = server.add_api_key(content).await; | ||||
|     assert_eq!(code, 201); | ||||
|     assert!(response["key"].is_string()); | ||||
|  | ||||
|     let key = response["key"].as_str().unwrap(); | ||||
|     server.use_api_key(&key); | ||||
|  | ||||
|     for (method, route) in AUTHORIZATIONS.keys() { | ||||
|         let (response, code) = server.dummy_request(method, route).await; | ||||
|  | ||||
|         assert_eq!(response, INVALID_RESPONSE.clone()); | ||||
|         assert_eq!(code, 403); | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[actix_rt::test] | ||||
| async fn error_access_unauthorized_index() { | ||||
|     let mut server = Server::new_auth().await; | ||||
|     server.use_api_key("MASTER_KEY"); | ||||
|  | ||||
|     let content = json!({ | ||||
|         "indexes": ["sales"], | ||||
|         "actions": ALL_ACTIONS.clone(), | ||||
|         "expiresAt": "2050-11-13T00:00:00Z" | ||||
|     }); | ||||
|  | ||||
|     let (response, code) = server.add_api_key(content).await; | ||||
|     assert_eq!(code, 201); | ||||
|     assert!(response["key"].is_string()); | ||||
|  | ||||
|     let key = response["key"].as_str().unwrap(); | ||||
|     server.use_api_key(&key); | ||||
|  | ||||
|     for (method, route) in AUTHORIZATIONS | ||||
|         .keys() | ||||
|         // filter `products` index routes | ||||
|         .filter(|(_, route)| route.starts_with("/indexes/products")) | ||||
|     { | ||||
|         let (response, code) = server.dummy_request(method, route).await; | ||||
|  | ||||
|         assert_eq!(response, INVALID_RESPONSE.clone()); | ||||
|         assert_eq!(code, 403); | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[actix_rt::test] | ||||
| async fn error_access_unauthorized_action() { | ||||
|     let mut server = Server::new_auth().await; | ||||
|     server.use_api_key("MASTER_KEY"); | ||||
|  | ||||
|     let content = json!({ | ||||
|         "indexes": ["products"], | ||||
|         "actions": [], | ||||
|         "expiresAt": "2050-11-13T00:00:00Z" | ||||
|     }); | ||||
|  | ||||
|     let (response, code) = server.add_api_key(content).await; | ||||
|     assert_eq!(code, 201); | ||||
|     assert!(response["key"].is_string()); | ||||
|  | ||||
|     let key = response["key"].as_str().unwrap(); | ||||
|     server.use_api_key(&key); | ||||
|  | ||||
|     for ((method, route), action) in AUTHORIZATIONS.iter() { | ||||
|         server.use_api_key("MASTER_KEY"); | ||||
|  | ||||
|         // Patch API key letting all rights but the needed one. | ||||
|         let content = json!({ | ||||
|             "actions": ALL_ACTIONS.iter().cloned().filter(|a| a != action).collect::<Vec<_>>(), | ||||
|         }); | ||||
|         let (_, code) = server.patch_api_key(&key, content).await; | ||||
|         assert_eq!(code, 200); | ||||
|  | ||||
|         server.use_api_key(&key); | ||||
|         let (response, code) = server.dummy_request(method, route).await; | ||||
|  | ||||
|         assert_eq!(response, INVALID_RESPONSE.clone()); | ||||
|         assert_eq!(code, 403); | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[actix_rt::test] | ||||
| async fn access_authorized_restricted_index() { | ||||
|     let mut server = Server::new_auth().await; | ||||
|     server.use_api_key("MASTER_KEY"); | ||||
|  | ||||
|     let content = json!({ | ||||
|         "indexes": ["products"], | ||||
|         "actions": [], | ||||
|         "expiresAt": "2050-11-13T00:00:00Z" | ||||
|     }); | ||||
|  | ||||
|     let (response, code) = server.add_api_key(content).await; | ||||
|     assert_eq!(code, 201); | ||||
|     assert!(response["key"].is_string()); | ||||
|  | ||||
|     let key = response["key"].as_str().unwrap(); | ||||
|     server.use_api_key(&key); | ||||
|  | ||||
|     for ((method, route), action) in AUTHORIZATIONS.iter() { | ||||
|         // Patch API key letting only the needed action. | ||||
|         let content = json!({ | ||||
|             "actions": [action], | ||||
|         }); | ||||
|  | ||||
|         server.use_api_key("MASTER_KEY"); | ||||
|         let (_, code) = server.patch_api_key(&key, content).await; | ||||
|         assert_eq!(code, 200); | ||||
|  | ||||
|         server.use_api_key(&key); | ||||
|         let (response, code) = server.dummy_request(method, route).await; | ||||
|  | ||||
|         assert_ne!(response, INVALID_RESPONSE.clone()); | ||||
|         assert_ne!(code, 403); | ||||
|  | ||||
|         // Patch API key using action all action. | ||||
|         let content = json!({ | ||||
|             "actions": ["*"], | ||||
|         }); | ||||
|  | ||||
|         server.use_api_key("MASTER_KEY"); | ||||
|         let (_, code) = server.patch_api_key(&key, content).await; | ||||
|         assert_eq!(code, 200); | ||||
|  | ||||
|         server.use_api_key(&key); | ||||
|         let (response, code) = server.dummy_request(method, route).await; | ||||
|  | ||||
|         assert_ne!(response, INVALID_RESPONSE.clone()); | ||||
|         assert_ne!(code, 403); | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[actix_rt::test] | ||||
| async fn access_authorized_no_index_restriction() { | ||||
|     let mut server = Server::new_auth().await; | ||||
|     server.use_api_key("MASTER_KEY"); | ||||
|  | ||||
|     let content = json!({ | ||||
|         "indexes": ["*"], | ||||
|         "actions": [], | ||||
|         "expiresAt": "2050-11-13T00:00:00Z" | ||||
|     }); | ||||
|  | ||||
|     let (response, code) = server.add_api_key(content).await; | ||||
|     assert_eq!(code, 201); | ||||
|     assert!(response["key"].is_string()); | ||||
|  | ||||
|     let key = response["key"].as_str().unwrap(); | ||||
|     server.use_api_key(&key); | ||||
|  | ||||
|     for ((method, route), action) in AUTHORIZATIONS.iter() { | ||||
|         server.use_api_key("MASTER_KEY"); | ||||
|  | ||||
|         // Patch API key letting only the needed action. | ||||
|         let content = json!({ | ||||
|             "actions": [action], | ||||
|         }); | ||||
|         let (_, code) = server.patch_api_key(&key, content).await; | ||||
|         assert_eq!(code, 200); | ||||
|  | ||||
|         server.use_api_key(&key); | ||||
|         let (response, code) = server.dummy_request(method, route).await; | ||||
|  | ||||
|         assert_ne!(response, INVALID_RESPONSE.clone()); | ||||
|         assert_ne!(code, 403); | ||||
|  | ||||
|         // Patch API key using action all action. | ||||
|         let content = json!({ | ||||
|             "actions": ["*"], | ||||
|         }); | ||||
|  | ||||
|         server.use_api_key("MASTER_KEY"); | ||||
|         let (_, code) = server.patch_api_key(&key, content).await; | ||||
|         assert_eq!(code, 200); | ||||
|  | ||||
|         server.use_api_key(&key); | ||||
|         let (response, code) = server.dummy_request(method, route).await; | ||||
|  | ||||
|         assert_ne!(response, INVALID_RESPONSE.clone()); | ||||
|         assert_ne!(code, 403); | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[actix_rt::test] | ||||
| async fn access_authorized_stats_restricted_index() { | ||||
|     let mut server = Server::new_auth().await; | ||||
|     server.use_api_key("MASTER_KEY"); | ||||
|  | ||||
|     // create index `test` | ||||
|     let index = server.index("test"); | ||||
|     let (_, code) = index.create(Some("id")).await; | ||||
|     assert_eq!(code, 202); | ||||
|     // create index `products` | ||||
|     let index = server.index("products"); | ||||
|     let (_, code) = index.create(Some("product_id")).await; | ||||
|     assert_eq!(code, 202); | ||||
|     index.wait_task(0).await; | ||||
|  | ||||
|     // create key with access on `products` index only. | ||||
|     let content = json!({ | ||||
|         "indexes": ["products"], | ||||
|         "actions": ["stats.get"], | ||||
|         "expiresAt": "2050-11-13T00:00:00Z" | ||||
|     }); | ||||
|     let (response, code) = server.add_api_key(content).await; | ||||
|     assert_eq!(code, 201); | ||||
|     assert!(response["key"].is_string()); | ||||
|  | ||||
|     // use created key. | ||||
|     let key = response["key"].as_str().unwrap(); | ||||
|     server.use_api_key(&key); | ||||
|  | ||||
|     let (response, code) = server.stats().await; | ||||
|     assert_eq!(code, 200); | ||||
|  | ||||
|     // key should have access on `products` index. | ||||
|     assert!(response["indexes"].get("products").is_some()); | ||||
|  | ||||
|     // key should not have access on `test` index. | ||||
|     assert!(response["indexes"].get("test").is_none()); | ||||
| } | ||||
|  | ||||
| #[actix_rt::test] | ||||
| async fn access_authorized_stats_no_index_restriction() { | ||||
|     let mut server = Server::new_auth().await; | ||||
|     server.use_api_key("MASTER_KEY"); | ||||
|  | ||||
|     // create index `test` | ||||
|     let index = server.index("test"); | ||||
|     let (_, code) = index.create(Some("id")).await; | ||||
|     assert_eq!(code, 202); | ||||
|     // create index `products` | ||||
|     let index = server.index("products"); | ||||
|     let (_, code) = index.create(Some("product_id")).await; | ||||
|     assert_eq!(code, 202); | ||||
|     index.wait_task(0).await; | ||||
|  | ||||
|     // create key with access on all indexes. | ||||
|     let content = json!({ | ||||
|         "indexes": ["*"], | ||||
|         "actions": ["stats.get"], | ||||
|         "expiresAt": "2050-11-13T00:00:00Z" | ||||
|     }); | ||||
|     let (response, code) = server.add_api_key(content).await; | ||||
|     assert_eq!(code, 201); | ||||
|     assert!(response["key"].is_string()); | ||||
|  | ||||
|     // use created key. | ||||
|     let key = response["key"].as_str().unwrap(); | ||||
|     server.use_api_key(&key); | ||||
|  | ||||
|     let (response, code) = server.stats().await; | ||||
|     assert_eq!(code, 200); | ||||
|  | ||||
|     // key should have access on `products` index. | ||||
|     assert!(response["indexes"].get("products").is_some()); | ||||
|  | ||||
|     // key should have access on `test` index. | ||||
|     assert!(response["indexes"].get("test").is_some()); | ||||
| } | ||||
|  | ||||
| #[actix_rt::test] | ||||
| async fn list_authorized_indexes_restricted_index() { | ||||
|     let mut server = Server::new_auth().await; | ||||
|     server.use_api_key("MASTER_KEY"); | ||||
|  | ||||
|     // create index `test` | ||||
|     let index = server.index("test"); | ||||
|     let (_, code) = index.create(Some("id")).await; | ||||
|     assert_eq!(code, 202); | ||||
|     // create index `products` | ||||
|     let index = server.index("products"); | ||||
|     let (_, code) = index.create(Some("product_id")).await; | ||||
|     assert_eq!(code, 202); | ||||
|     index.wait_task(0).await; | ||||
|  | ||||
|     // create key with access on `products` index only. | ||||
|     let content = json!({ | ||||
|         "indexes": ["products"], | ||||
|         "actions": ["indexes.get"], | ||||
|         "expiresAt": "2050-11-13T00:00:00Z" | ||||
|     }); | ||||
|     let (response, code) = server.add_api_key(content).await; | ||||
|     assert_eq!(code, 201); | ||||
|     assert!(response["key"].is_string()); | ||||
|  | ||||
|     // use created key. | ||||
|     let key = response["key"].as_str().unwrap(); | ||||
|     server.use_api_key(&key); | ||||
|  | ||||
|     let (response, code) = server.list_indexes().await; | ||||
|     assert_eq!(code, 200); | ||||
|  | ||||
|     let response = response.as_array().unwrap(); | ||||
|     // key should have access on `products` index. | ||||
|     assert!(response.iter().any(|index| index["uid"] == "products")); | ||||
|  | ||||
|     // key should not have access on `test` index. | ||||
|     assert!(!response.iter().any(|index| index["uid"] == "test")); | ||||
| } | ||||
|  | ||||
| #[actix_rt::test] | ||||
| async fn list_authorized_indexes_no_index_restriction() { | ||||
|     let mut server = Server::new_auth().await; | ||||
|     server.use_api_key("MASTER_KEY"); | ||||
|  | ||||
|     // create index `test` | ||||
|     let index = server.index("test"); | ||||
|     let (_, code) = index.create(Some("id")).await; | ||||
|     assert_eq!(code, 202); | ||||
|     // create index `products` | ||||
|     let index = server.index("products"); | ||||
|     let (_, code) = index.create(Some("product_id")).await; | ||||
|     assert_eq!(code, 202); | ||||
|     index.wait_task(0).await; | ||||
|  | ||||
|     // create key with access on all indexes. | ||||
|     let content = json!({ | ||||
|         "indexes": ["*"], | ||||
|         "actions": ["indexes.get"], | ||||
|         "expiresAt": "2050-11-13T00:00:00Z" | ||||
|     }); | ||||
|     let (response, code) = server.add_api_key(content).await; | ||||
|     assert_eq!(code, 201); | ||||
|     assert!(response["key"].is_string()); | ||||
|  | ||||
|     // use created key. | ||||
|     let key = response["key"].as_str().unwrap(); | ||||
|     server.use_api_key(&key); | ||||
|  | ||||
|     let (response, code) = server.list_indexes().await; | ||||
|     assert_eq!(code, 200); | ||||
|  | ||||
|     let response = response.as_array().unwrap(); | ||||
|     // key should have access on `products` index. | ||||
|     assert!(response.iter().any(|index| index["uid"] == "products")); | ||||
|  | ||||
|     // key should have access on `test` index. | ||||
|     assert!(response.iter().any(|index| index["uid"] == "test")); | ||||
| } | ||||
							
								
								
									
										71
									
								
								meilisearch-http/tests/auth/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								meilisearch-http/tests/auth/mod.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,71 @@ | ||||
| mod api_keys; | ||||
| mod authorization; | ||||
| mod payload; | ||||
|  | ||||
| use crate::common::server::default_settings; | ||||
| use crate::common::server::TEST_TEMP_DIR; | ||||
| use crate::common::Server; | ||||
| use actix_web::http::StatusCode; | ||||
| use serde_json::{json, Value}; | ||||
| use tempfile::TempDir; | ||||
|  | ||||
| impl Server { | ||||
|     pub async fn new_auth() -> Self { | ||||
|         let dir = TempDir::new().unwrap(); | ||||
|  | ||||
|         if cfg!(windows) { | ||||
|             std::env::set_var("TMP", TEST_TEMP_DIR.path()); | ||||
|         } else { | ||||
|             std::env::set_var("TMPDIR", TEST_TEMP_DIR.path()); | ||||
|         } | ||||
|  | ||||
|         let mut options = default_settings(dir.path()); | ||||
|         options.master_key = Some("MASTER_KEY".to_string()); | ||||
|  | ||||
|         Self::new_with_options(options).await | ||||
|     } | ||||
|  | ||||
|     pub fn use_api_key(&mut self, api_key: impl AsRef<str>) { | ||||
|         self.service.api_key = Some(api_key.as_ref().to_string()); | ||||
|     } | ||||
|  | ||||
|     pub async fn add_api_key(&self, content: Value) -> (Value, StatusCode) { | ||||
|         let url = "/keys"; | ||||
|         self.service.post(url, content).await | ||||
|     } | ||||
|  | ||||
|     pub async fn get_api_key(&self, key: impl AsRef<str>) -> (Value, StatusCode) { | ||||
|         let url = format!("/keys/{}", key.as_ref()); | ||||
|         self.service.get(url).await | ||||
|     } | ||||
|  | ||||
|     pub async fn patch_api_key(&self, key: impl AsRef<str>, content: Value) -> (Value, StatusCode) { | ||||
|         let url = format!("/keys/{}", key.as_ref()); | ||||
|         self.service.patch(url, content).await | ||||
|     } | ||||
|  | ||||
|     pub async fn list_api_keys(&self) -> (Value, StatusCode) { | ||||
|         let url = "/keys"; | ||||
|         self.service.get(url).await | ||||
|     } | ||||
|  | ||||
|     pub async fn delete_api_key(&self, key: impl AsRef<str>) -> (Value, StatusCode) { | ||||
|         let url = format!("/keys/{}", key.as_ref()); | ||||
|         self.service.delete(url).await | ||||
|     } | ||||
|  | ||||
|     pub async fn dummy_request( | ||||
|         &self, | ||||
|         method: impl AsRef<str>, | ||||
|         url: impl AsRef<str>, | ||||
|     ) -> (Value, StatusCode) { | ||||
|         match method.as_ref() { | ||||
|             "POST" => self.service.post(url, json!({})).await, | ||||
|             "PUT" => self.service.put(url, json!({})).await, | ||||
|             "PATCH" => self.service.patch(url, json!({})).await, | ||||
|             "GET" => self.service.get(url).await, | ||||
|             "DELETE" => self.service.delete(url).await, | ||||
|             _ => unreachable!(), | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										340
									
								
								meilisearch-http/tests/auth/payload.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										340
									
								
								meilisearch-http/tests/auth/payload.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,340 @@ | ||||
| use crate::common::Server; | ||||
| use actix_web::test; | ||||
| use meilisearch_http::{analytics, create_app}; | ||||
| use serde_json::{json, Value}; | ||||
|  | ||||
| #[actix_rt::test] | ||||
| async fn error_api_key_bad_content_types() { | ||||
|     let content = json!({ | ||||
|         "indexes": ["products"], | ||||
|         "actions": [ | ||||
|             "documents.add" | ||||
|         ], | ||||
|         "expiresAt": "2050-11-13T00:00:00Z" | ||||
|     }); | ||||
|  | ||||
|     let mut server = Server::new_auth().await; | ||||
|     server.use_api_key("MASTER_KEY"); | ||||
|     let app = test::init_service(create_app!( | ||||
|         &server.service.meilisearch, | ||||
|         &server.service.auth, | ||||
|         true, | ||||
|         &server.service.options, | ||||
|         analytics::MockAnalytics::new(&server.service.options).0 | ||||
|     )) | ||||
|     .await; | ||||
|  | ||||
|     // post | ||||
|     let req = test::TestRequest::post() | ||||
|         .uri("/keys") | ||||
|         .set_payload(content.to_string()) | ||||
|         .insert_header(("content-type", "text/plain")) | ||||
|         .insert_header(("Authorization", "Bearer MASTER_KEY")) | ||||
|         .to_request(); | ||||
|     let res = test::call_service(&app, req).await; | ||||
|     let status_code = res.status(); | ||||
|     let body = test::read_body(res).await; | ||||
|     let response: Value = serde_json::from_slice(&body).unwrap_or_default(); | ||||
|     assert_eq!(status_code, 415); | ||||
|     assert_eq!( | ||||
|         response["message"], | ||||
|         json!( | ||||
|             r#"The Content-Type `text/plain` is invalid. Accepted values for the Content-Type header are: `application/json`"# | ||||
|         ) | ||||
|     ); | ||||
|     assert_eq!(response["code"], "invalid_content_type"); | ||||
|     assert_eq!(response["type"], "invalid_request"); | ||||
|     assert_eq!( | ||||
|         response["link"], | ||||
|         "https://docs.meilisearch.com/errors#invalid_content_type" | ||||
|     ); | ||||
|  | ||||
|     // patch | ||||
|     let req = test::TestRequest::patch() | ||||
|         .uri("/keys/d0552b41536279a0ad88bd595327b96f01176a60c2243e906c52ac02375f9bc4") | ||||
|         .set_payload(content.to_string()) | ||||
|         .insert_header(("content-type", "text/plain")) | ||||
|         .insert_header(("Authorization", "Bearer MASTER_KEY")) | ||||
|         .to_request(); | ||||
|     let res = test::call_service(&app, req).await; | ||||
|     let status_code = res.status(); | ||||
|     let body = test::read_body(res).await; | ||||
|     let response: Value = serde_json::from_slice(&body).unwrap_or_default(); | ||||
|     assert_eq!(status_code, 415); | ||||
|     assert_eq!( | ||||
|         response["message"], | ||||
|         json!( | ||||
|             r#"The Content-Type `text/plain` is invalid. Accepted values for the Content-Type header are: `application/json`"# | ||||
|         ) | ||||
|     ); | ||||
|     assert_eq!(response["code"], "invalid_content_type"); | ||||
|     assert_eq!(response["type"], "invalid_request"); | ||||
|     assert_eq!( | ||||
|         response["link"], | ||||
|         "https://docs.meilisearch.com/errors#invalid_content_type" | ||||
|     ); | ||||
| } | ||||
|  | ||||
| #[actix_rt::test] | ||||
| async fn error_api_key_empty_content_types() { | ||||
|     let content = json!({ | ||||
|         "indexes": ["products"], | ||||
|         "actions": [ | ||||
|             "documents.add" | ||||
|         ], | ||||
|         "expiresAt": "2050-11-13T00:00:00Z" | ||||
|     }); | ||||
|  | ||||
|     let mut server = Server::new_auth().await; | ||||
|     server.use_api_key("MASTER_KEY"); | ||||
|     let app = test::init_service(create_app!( | ||||
|         &server.service.meilisearch, | ||||
|         &server.service.auth, | ||||
|         true, | ||||
|         &server.service.options, | ||||
|         analytics::MockAnalytics::new(&server.service.options).0 | ||||
|     )) | ||||
|     .await; | ||||
|  | ||||
|     // post | ||||
|     let req = test::TestRequest::post() | ||||
|         .uri("/keys") | ||||
|         .set_payload(content.to_string()) | ||||
|         .insert_header(("content-type", "")) | ||||
|         .insert_header(("Authorization", "Bearer MASTER_KEY")) | ||||
|         .to_request(); | ||||
|     let res = test::call_service(&app, req).await; | ||||
|     let status_code = res.status(); | ||||
|     let body = test::read_body(res).await; | ||||
|     let response: Value = serde_json::from_slice(&body).unwrap_or_default(); | ||||
|     assert_eq!(status_code, 415); | ||||
|     assert_eq!( | ||||
|         response["message"], | ||||
|         json!( | ||||
|             r#"The Content-Type `` is invalid. Accepted values for the Content-Type header are: `application/json`"# | ||||
|         ) | ||||
|     ); | ||||
|     assert_eq!(response["code"], "invalid_content_type"); | ||||
|     assert_eq!(response["type"], "invalid_request"); | ||||
|     assert_eq!( | ||||
|         response["link"], | ||||
|         "https://docs.meilisearch.com/errors#invalid_content_type" | ||||
|     ); | ||||
|  | ||||
|     // patch | ||||
|     let req = test::TestRequest::patch() | ||||
|         .uri("/keys/d0552b41536279a0ad88bd595327b96f01176a60c2243e906c52ac02375f9bc4") | ||||
|         .set_payload(content.to_string()) | ||||
|         .insert_header(("content-type", "")) | ||||
|         .insert_header(("Authorization", "Bearer MASTER_KEY")) | ||||
|         .to_request(); | ||||
|     let res = test::call_service(&app, req).await; | ||||
|     let status_code = res.status(); | ||||
|     let body = test::read_body(res).await; | ||||
|     let response: Value = serde_json::from_slice(&body).unwrap_or_default(); | ||||
|     assert_eq!(status_code, 415); | ||||
|     assert_eq!( | ||||
|         response["message"], | ||||
|         json!( | ||||
|             r#"The Content-Type `` is invalid. Accepted values for the Content-Type header are: `application/json`"# | ||||
|         ) | ||||
|     ); | ||||
|     assert_eq!(response["code"], "invalid_content_type"); | ||||
|     assert_eq!(response["type"], "invalid_request"); | ||||
|     assert_eq!( | ||||
|         response["link"], | ||||
|         "https://docs.meilisearch.com/errors#invalid_content_type" | ||||
|     ); | ||||
| } | ||||
|  | ||||
| #[actix_rt::test] | ||||
| async fn error_api_key_missing_content_types() { | ||||
|     let content = json!({ | ||||
|         "indexes": ["products"], | ||||
|         "actions": [ | ||||
|             "documents.add" | ||||
|         ], | ||||
|         "expiresAt": "2050-11-13T00:00:00Z" | ||||
|     }); | ||||
|  | ||||
|     let mut server = Server::new_auth().await; | ||||
|     server.use_api_key("MASTER_KEY"); | ||||
|     let app = test::init_service(create_app!( | ||||
|         &server.service.meilisearch, | ||||
|         &server.service.auth, | ||||
|         true, | ||||
|         &server.service.options, | ||||
|         analytics::MockAnalytics::new(&server.service.options).0 | ||||
|     )) | ||||
|     .await; | ||||
|  | ||||
|     // post | ||||
|     let req = test::TestRequest::post() | ||||
|         .uri("/keys") | ||||
|         .set_payload(content.to_string()) | ||||
|         .insert_header(("Authorization", "Bearer MASTER_KEY")) | ||||
|         .to_request(); | ||||
|     let res = test::call_service(&app, req).await; | ||||
|     let status_code = res.status(); | ||||
|     let body = test::read_body(res).await; | ||||
|     let response: Value = serde_json::from_slice(&body).unwrap_or_default(); | ||||
|     assert_eq!(status_code, 415); | ||||
|     assert_eq!( | ||||
|         response["message"], | ||||
|         json!( | ||||
|             r#"A Content-Type header is missing. Accepted values for the Content-Type header are: `application/json`"# | ||||
|         ) | ||||
|     ); | ||||
|     assert_eq!(response["code"], "missing_content_type"); | ||||
|     assert_eq!(response["type"], "invalid_request"); | ||||
|     assert_eq!( | ||||
|         response["link"], | ||||
|         "https://docs.meilisearch.com/errors#missing_content_type" | ||||
|     ); | ||||
|  | ||||
|     // patch | ||||
|     let req = test::TestRequest::patch() | ||||
|         .uri("/keys/d0552b41536279a0ad88bd595327b96f01176a60c2243e906c52ac02375f9bc4") | ||||
|         .set_payload(content.to_string()) | ||||
|         .insert_header(("Authorization", "Bearer MASTER_KEY")) | ||||
|         .to_request(); | ||||
|     let res = test::call_service(&app, req).await; | ||||
|     let status_code = res.status(); | ||||
|     let body = test::read_body(res).await; | ||||
|     let response: Value = serde_json::from_slice(&body).unwrap_or_default(); | ||||
|     assert_eq!(status_code, 415); | ||||
|     assert_eq!( | ||||
|         response["message"], | ||||
|         json!( | ||||
|             r#"A Content-Type header is missing. Accepted values for the Content-Type header are: `application/json`"# | ||||
|         ) | ||||
|     ); | ||||
|     assert_eq!(response["code"], "missing_content_type"); | ||||
|     assert_eq!(response["type"], "invalid_request"); | ||||
|     assert_eq!( | ||||
|         response["link"], | ||||
|         "https://docs.meilisearch.com/errors#missing_content_type" | ||||
|     ); | ||||
| } | ||||
|  | ||||
| #[actix_rt::test] | ||||
| async fn error_api_key_empty_payload() { | ||||
|     let content = ""; | ||||
|  | ||||
|     let mut server = Server::new_auth().await; | ||||
|     server.use_api_key("MASTER_KEY"); | ||||
|     let app = test::init_service(create_app!( | ||||
|         &server.service.meilisearch, | ||||
|         &server.service.auth, | ||||
|         true, | ||||
|         &server.service.options, | ||||
|         analytics::MockAnalytics::new(&server.service.options).0 | ||||
|     )) | ||||
|     .await; | ||||
|  | ||||
|     // post | ||||
|     let req = test::TestRequest::post() | ||||
|         .uri("/keys") | ||||
|         .set_payload(content) | ||||
|         .insert_header(("Authorization", "Bearer MASTER_KEY")) | ||||
|         .insert_header(("content-type", "application/json")) | ||||
|         .to_request(); | ||||
|     let res = test::call_service(&app, req).await; | ||||
|     let status_code = res.status(); | ||||
|     let body = test::read_body(res).await; | ||||
|     let response: Value = serde_json::from_slice(&body).unwrap_or_default(); | ||||
|     assert_eq!(status_code, 400); | ||||
|     assert_eq!(response["code"], json!("missing_payload")); | ||||
|     assert_eq!(response["type"], json!("invalid_request")); | ||||
|     assert_eq!( | ||||
|         response["link"], | ||||
|         json!("https://docs.meilisearch.com/errors#missing_payload") | ||||
|     ); | ||||
|     assert_eq!(response["message"], json!(r#"A json payload is missing."#)); | ||||
|  | ||||
|     // patch | ||||
|     let req = test::TestRequest::patch() | ||||
|         .uri("/keys/d0552b41536279a0ad88bd595327b96f01176a60c2243e906c52ac02375f9bc4") | ||||
|         .set_payload(content) | ||||
|         .insert_header(("Authorization", "Bearer MASTER_KEY")) | ||||
|         .insert_header(("content-type", "application/json")) | ||||
|         .to_request(); | ||||
|     let res = test::call_service(&app, req).await; | ||||
|     let status_code = res.status(); | ||||
|     let body = test::read_body(res).await; | ||||
|     let response: Value = serde_json::from_slice(&body).unwrap_or_default(); | ||||
|     assert_eq!(status_code, 400); | ||||
|     assert_eq!(response["code"], json!("missing_payload")); | ||||
|     assert_eq!(response["type"], json!("invalid_request")); | ||||
|     assert_eq!( | ||||
|         response["link"], | ||||
|         json!("https://docs.meilisearch.com/errors#missing_payload") | ||||
|     ); | ||||
|     assert_eq!(response["message"], json!(r#"A json payload is missing."#)); | ||||
| } | ||||
|  | ||||
| #[actix_rt::test] | ||||
| async fn error_api_key_malformed_payload() { | ||||
|     let content = r#"{"malormed": "payload""#; | ||||
|  | ||||
|     let mut server = Server::new_auth().await; | ||||
|     server.use_api_key("MASTER_KEY"); | ||||
|     let app = test::init_service(create_app!( | ||||
|         &server.service.meilisearch, | ||||
|         &server.service.auth, | ||||
|         true, | ||||
|         &server.service.options, | ||||
|         analytics::MockAnalytics::new(&server.service.options).0 | ||||
|     )) | ||||
|     .await; | ||||
|  | ||||
|     // post | ||||
|     let req = test::TestRequest::post() | ||||
|         .uri("/keys") | ||||
|         .set_payload(content) | ||||
|         .insert_header(("Authorization", "Bearer MASTER_KEY")) | ||||
|         .insert_header(("content-type", "application/json")) | ||||
|         .to_request(); | ||||
|     let res = test::call_service(&app, req).await; | ||||
|     let status_code = res.status(); | ||||
|     let body = test::read_body(res).await; | ||||
|     let response: Value = serde_json::from_slice(&body).unwrap_or_default(); | ||||
|     assert_eq!(status_code, 400); | ||||
|     assert_eq!(response["code"], json!("malformed_payload")); | ||||
|     assert_eq!(response["type"], json!("invalid_request")); | ||||
|     assert_eq!( | ||||
|         response["link"], | ||||
|         json!("https://docs.meilisearch.com/errors#malformed_payload") | ||||
|     ); | ||||
|     assert_eq!( | ||||
|         response["message"], | ||||
|         json!( | ||||
|             r#"The json payload provided is malformed. `EOF while parsing an object at line 1 column 22`."# | ||||
|         ) | ||||
|     ); | ||||
|  | ||||
|     // patch | ||||
|     let req = test::TestRequest::patch() | ||||
|         .uri("/keys/d0552b41536279a0ad88bd595327b96f01176a60c2243e906c52ac02375f9bc4") | ||||
|         .set_payload(content) | ||||
|         .insert_header(("Authorization", "Bearer MASTER_KEY")) | ||||
|         .insert_header(("content-type", "application/json")) | ||||
|         .to_request(); | ||||
|     let res = test::call_service(&app, req).await; | ||||
|     let status_code = res.status(); | ||||
|     let body = test::read_body(res).await; | ||||
|     let response: Value = serde_json::from_slice(&body).unwrap_or_default(); | ||||
|     assert_eq!(status_code, 400); | ||||
|     assert_eq!(response["code"], json!("malformed_payload")); | ||||
|     assert_eq!(response["type"], json!("invalid_request")); | ||||
|     assert_eq!( | ||||
|         response["link"], | ||||
|         json!("https://docs.meilisearch.com/errors#malformed_payload") | ||||
|     ); | ||||
|     assert_eq!( | ||||
|         response["message"], | ||||
|         json!( | ||||
|             r#"The json payload provided is malformed. `EOF while parsing an object at line 1 column 22`."# | ||||
|         ) | ||||
|     ); | ||||
| } | ||||
		Reference in New Issue
	
	Block a user