diff --git a/meilisearch-http/src/routes/search.rs b/meilisearch-http/src/routes/search.rs index cc1306e71..2fbbfe4b3 100644 --- a/meilisearch-http/src/routes/search.rs +++ b/meilisearch-http/src/routes/search.rs @@ -1,18 +1,129 @@ -use std::collections::{BTreeSet, HashSet}; +use std::any::{Any, TypeId}; +use std::collections::{BTreeSet, HashMap, HashSet}; +use std::marker::PhantomData; +use std::ops::Deref; -use actix_web::{get, post, web, HttpResponse}; use log::debug; +use actix_web::{web, FromRequest, HttpResponse}; +use futures::future::{err, ok, Ready}; use serde::Deserialize; use serde_json::Value; -use crate::error::ResponseError; -use crate::helpers::Authentication; +use crate::error::{AuthenticationError, ResponseError}; use crate::index::{default_crop_length, SearchQuery, DEFAULT_SEARCH_LIMIT}; use crate::routes::IndexParam; use crate::Data; +struct Public; + +impl Policy for Public { + fn authenticate(&self, _token: &[u8]) -> bool { + true + } +} + +struct GuardedData { + data: D, + _marker: PhantomData, +} + +trait Policy { + fn authenticate(&self, token: &[u8]) -> bool; +} + +struct Policies { + inner: HashMap>, +} + +impl Policies { + fn new() -> Self { + Self { inner: HashMap::new() } + } + + fn insert(&mut self, policy: S) { + self.inner.insert(TypeId::of::(), Box::new(policy)); + } + + fn get(&self) -> Option<&S> { + self.inner + .get(&TypeId::of::()) + .and_then(|p| p.downcast_ref::()) + } +} + +enum AuthConfig { + NoAuth, + Auth(Policies), +} + +impl Default for AuthConfig { + fn default() -> Self { + Self::NoAuth + } +} + +impl FromRequest for GuardedData { + type Config = AuthConfig; + + type Error = ResponseError; + + type Future = Ready>; + + fn from_request( + req: &actix_web::HttpRequest, + _payload: &mut actix_http::Payload, + ) -> Self::Future { + match req.app_data::() { + Some(config) => match config { + AuthConfig::NoAuth => match req.app_data::().cloned() { + Some(data) => ok(Self { + data, + _marker: PhantomData, + }), + None => todo!("Data not configured"), + }, + AuthConfig::Auth(policies) => match policies.get::

() { + Some(policy) => match req.headers().get("x-meili-api-key") { + Some(token) => { + if policy.authenticate(token.as_bytes()) { + match req.app_data::().cloned() { + Some(data) => ok(Self { + data, + _marker: PhantomData, + }), + None => todo!("Data not configured"), + } + } else { + err(AuthenticationError::InvalidToken(String::from("hello")).into()) + } + } + None => err(AuthenticationError::MissingAuthorizationHeader.into()), + }, + None => todo!("no policy found"), + }, + }, + None => todo!(), + } + } +} + +impl Deref for GuardedData { + type Target = D; + + fn deref(&self) -> &Self::Target { + &self.data + } +} + pub fn services(cfg: &mut web::ServiceConfig) { - cfg.service(search_with_post).service(search_with_url_query); + let mut policies = Policies::new(); + policies.insert(Public); + cfg.service( + web::resource("/indexes/{index_uid}/search") + .app_data(AuthConfig::Auth(policies)) + .route(web::get().to(search_with_url_query)) + .route(web::post().to(search_with_post)), + ); } #[derive(Deserialize, Debug)] @@ -73,9 +184,8 @@ impl From for SearchQuery { } } -#[get("/indexes/{index_uid}/search", wrap = "Authentication::Public")] async fn search_with_url_query( - data: web::Data, + data: GuardedData, path: web::Path, params: web::Query, ) -> Result { @@ -86,9 +196,8 @@ async fn search_with_url_query( Ok(HttpResponse::Ok().json(search_result)) } -#[post("/indexes/{index_uid}/search", wrap = "Authentication::Public")] async fn search_with_post( - data: web::Data, + data: GuardedData, path: web::Path, params: web::Json, ) -> Result {