mirror of
				https://github.com/meilisearch/meilisearch.git
				synced 2025-10-30 23:46:28 +00:00 
			
		
		
		
	Add uid and name fields in keys
This commit is contained in:
		
							
								
								
									
										1
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							| @@ -1982,6 +1982,7 @@ dependencies = [ | |||||||
|  "sha2", |  "sha2", | ||||||
|  "thiserror", |  "thiserror", | ||||||
|  "time 0.3.9", |  "time 0.3.9", | ||||||
|  |  "uuid", | ||||||
| ] | ] | ||||||
|  |  | ||||||
| [[package]] | [[package]] | ||||||
|   | |||||||
| @@ -13,3 +13,4 @@ serde_json = { version = "1.0.79", features = ["preserve_order"] } | |||||||
| sha2 = "0.10.2" | sha2 = "0.10.2" | ||||||
| thiserror = "1.0.30" | thiserror = "1.0.30" | ||||||
| time = { version = "0.3.7", features = ["serde-well-known", "formatting", "parsing", "macros"] } | time = { version = "0.3.7", features = ["serde-well-known", "formatting", "parsing", "macros"] } | ||||||
|  | uuid = { version = "0.8.2", features = ["serde"] } | ||||||
|   | |||||||
| @@ -18,8 +18,16 @@ pub enum AuthControllerError { | |||||||
|     InvalidApiKeyExpiresAt(Value), |     InvalidApiKeyExpiresAt(Value), | ||||||
|     #[error("`description` field value `{0}` is invalid. It should be a string or specified as a null value.")] |     #[error("`description` field value `{0}` is invalid. It should be a string or specified as a null value.")] | ||||||
|     InvalidApiKeyDescription(Value), |     InvalidApiKeyDescription(Value), | ||||||
|  |     #[error( | ||||||
|  |         "`name` field value `{0}` is invalid. It should be a string or specified as a null value." | ||||||
|  |     )] | ||||||
|  |     InvalidApiKeyName(Value), | ||||||
|  |     #[error("`uid` field value `{0}` is invalid. It should be a valid uuidv4 string or ommited.")] | ||||||
|  |     InvalidApiKeyUid(Value), | ||||||
|     #[error("API key `{0}` not found.")] |     #[error("API key `{0}` not found.")] | ||||||
|     ApiKeyNotFound(String), |     ApiKeyNotFound(String), | ||||||
|  |     #[error("`uid` field value `{0}` already exists for an API key.")] | ||||||
|  |     ApiKeyAlreadyExists(String), | ||||||
|     #[error("Internal error: {0}")] |     #[error("Internal error: {0}")] | ||||||
|     Internal(Box<dyn Error + Send + Sync + 'static>), |     Internal(Box<dyn Error + Send + Sync + 'static>), | ||||||
| } | } | ||||||
| @@ -39,7 +47,10 @@ impl ErrorCode for AuthControllerError { | |||||||
|             Self::InvalidApiKeyIndexes(_) => Code::InvalidApiKeyIndexes, |             Self::InvalidApiKeyIndexes(_) => Code::InvalidApiKeyIndexes, | ||||||
|             Self::InvalidApiKeyExpiresAt(_) => Code::InvalidApiKeyExpiresAt, |             Self::InvalidApiKeyExpiresAt(_) => Code::InvalidApiKeyExpiresAt, | ||||||
|             Self::InvalidApiKeyDescription(_) => Code::InvalidApiKeyDescription, |             Self::InvalidApiKeyDescription(_) => Code::InvalidApiKeyDescription, | ||||||
|  |             Self::InvalidApiKeyName(_) => Code::InvalidApiKeyName, | ||||||
|             Self::ApiKeyNotFound(_) => Code::ApiKeyNotFound, |             Self::ApiKeyNotFound(_) => Code::ApiKeyNotFound, | ||||||
|  |             Self::InvalidApiKeyUid(_) => Code::InvalidApiKeyUid, | ||||||
|  |             Self::ApiKeyAlreadyExists(_) => Code::ApiKeyAlreadyExists, | ||||||
|             Self::Internal(_) => Code::Internal, |             Self::Internal(_) => Code::Internal, | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -1,18 +1,21 @@ | |||||||
| use crate::action::Action; | use crate::action::Action; | ||||||
| use crate::error::{AuthControllerError, Result}; | use crate::error::{AuthControllerError, Result}; | ||||||
| use crate::store::{KeyId, KEY_ID_LENGTH}; | use crate::store::KeyId; | ||||||
| use rand::Rng; |  | ||||||
| use serde::{Deserialize, Serialize}; | use serde::{Deserialize, Serialize}; | ||||||
| use serde_json::{from_value, Value}; | use serde_json::{from_value, Value}; | ||||||
| use time::format_description::well_known::Rfc3339; | use time::format_description::well_known::Rfc3339; | ||||||
| use time::macros::{format_description, time}; | use time::macros::{format_description, time}; | ||||||
| use time::{Date, OffsetDateTime, PrimitiveDateTime}; | use time::{Date, OffsetDateTime, PrimitiveDateTime}; | ||||||
|  | use uuid::Uuid; | ||||||
|  |  | ||||||
| #[derive(Debug, Deserialize, Serialize)] | #[derive(Debug, Deserialize, Serialize)] | ||||||
| pub struct Key { | pub struct Key { | ||||||
|     #[serde(skip_serializing_if = "Option::is_none")] |     #[serde(skip_serializing_if = "Option::is_none")] | ||||||
|     pub description: Option<String>, |     pub description: Option<String>, | ||||||
|     pub id: KeyId, |     #[serde(skip_serializing_if = "Option::is_none")] | ||||||
|  |     pub name: Option<String>, | ||||||
|  |     pub uid: KeyId, | ||||||
|     pub actions: Vec<Action>, |     pub actions: Vec<Action>, | ||||||
|     pub indexes: Vec<String>, |     pub indexes: Vec<String>, | ||||||
|     #[serde(with = "time::serde::rfc3339::option")] |     #[serde(with = "time::serde::rfc3339::option")] | ||||||
| @@ -25,6 +28,15 @@ pub struct Key { | |||||||
|  |  | ||||||
| impl Key { | impl Key { | ||||||
|     pub fn create_from_value(value: Value) -> Result<Self> { |     pub fn create_from_value(value: Value) -> Result<Self> { | ||||||
|  |         let name = match value.get("name") { | ||||||
|  |             Some(Value::Null) => None, | ||||||
|  |             Some(des) => Some( | ||||||
|  |                 from_value(des.clone()) | ||||||
|  |                     .map_err(|_| AuthControllerError::InvalidApiKeyName(des.clone()))?, | ||||||
|  |             ), | ||||||
|  |             None => None, | ||||||
|  |         }; | ||||||
|  |  | ||||||
|         let description = match value.get("description") { |         let description = match value.get("description") { | ||||||
|             Some(Value::Null) => None, |             Some(Value::Null) => None, | ||||||
|             Some(des) => Some( |             Some(des) => Some( | ||||||
| @@ -34,7 +46,13 @@ impl Key { | |||||||
|             None => None, |             None => None, | ||||||
|         }; |         }; | ||||||
|  |  | ||||||
|         let id = generate_id(); |         let uid = value.get("uid").map_or_else( | ||||||
|  |             || Ok(Uuid::new_v4()), | ||||||
|  |             |uid| { | ||||||
|  |                 from_value(uid.clone()) | ||||||
|  |                     .map_err(|_| AuthControllerError::InvalidApiKeyUid(uid.clone())) | ||||||
|  |             }, | ||||||
|  |         )?; | ||||||
|  |  | ||||||
|         let actions = value |         let actions = value | ||||||
|             .get("actions") |             .get("actions") | ||||||
| @@ -61,8 +79,9 @@ impl Key { | |||||||
|         let updated_at = created_at; |         let updated_at = created_at; | ||||||
|  |  | ||||||
|         Ok(Self { |         Ok(Self { | ||||||
|  |             name, | ||||||
|             description, |             description, | ||||||
|             id, |             uid, | ||||||
|             actions, |             actions, | ||||||
|             indexes, |             indexes, | ||||||
|             expires_at, |             expires_at, | ||||||
| @@ -101,9 +120,11 @@ impl Key { | |||||||
|  |  | ||||||
|     pub(crate) fn default_admin() -> Self { |     pub(crate) fn default_admin() -> Self { | ||||||
|         let now = OffsetDateTime::now_utc(); |         let now = OffsetDateTime::now_utc(); | ||||||
|  |         let uid = Uuid::new_v4(); | ||||||
|         Self { |         Self { | ||||||
|  |             name: Some("admin".to_string()), | ||||||
|             description: Some("Default Admin API Key (Use it for all other operations. Caution! Do not use it on a public frontend)".to_string()), |             description: Some("Default Admin API Key (Use it for all other operations. Caution! Do not use it on a public frontend)".to_string()), | ||||||
|             id: generate_id(), |             uid, | ||||||
|             actions: vec![Action::All], |             actions: vec![Action::All], | ||||||
|             indexes: vec!["*".to_string()], |             indexes: vec!["*".to_string()], | ||||||
|             expires_at: None, |             expires_at: None, | ||||||
| @@ -114,11 +135,13 @@ impl Key { | |||||||
|  |  | ||||||
|     pub(crate) fn default_search() -> Self { |     pub(crate) fn default_search() -> Self { | ||||||
|         let now = OffsetDateTime::now_utc(); |         let now = OffsetDateTime::now_utc(); | ||||||
|  |         let uid = Uuid::new_v4(); | ||||||
|         Self { |         Self { | ||||||
|  |             name: Some("search".to_string()), | ||||||
|             description: Some( |             description: Some( | ||||||
|                 "Default Search API Key (Use it to search from the frontend)".to_string(), |                 "Default Search API Key (Use it to search from the frontend)".to_string(), | ||||||
|             ), |             ), | ||||||
|             id: generate_id(), |             uid, | ||||||
|             actions: vec![Action::Search], |             actions: vec![Action::Search], | ||||||
|             indexes: vec!["*".to_string()], |             indexes: vec!["*".to_string()], | ||||||
|             expires_at: None, |             expires_at: None, | ||||||
| @@ -128,19 +151,6 @@ impl Key { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| /// Generate a printable key of 64 characters using thread_rng. |  | ||||||
| fn generate_id() -> [u8; KEY_ID_LENGTH] { |  | ||||||
|     const CHARSET: &[u8] = b"abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; |  | ||||||
|  |  | ||||||
|     let mut rng = rand::thread_rng(); |  | ||||||
|     let mut bytes = [0; KEY_ID_LENGTH]; |  | ||||||
|     for byte in bytes.iter_mut() { |  | ||||||
|         *byte = CHARSET[rng.gen_range(0..CHARSET.len())]; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     bytes |  | ||||||
| } |  | ||||||
|  |  | ||||||
| fn parse_expiration_date(value: &Value) -> Result<Option<OffsetDateTime>> { | fn parse_expiration_date(value: &Value) -> Result<Option<OffsetDateTime>> { | ||||||
|     match value { |     match value { | ||||||
|         Value::String(string) => OffsetDateTime::parse(string, &Rfc3339) |         Value::String(string) => OffsetDateTime::parse(string, &Rfc3339) | ||||||
|   | |||||||
| @@ -4,14 +4,15 @@ pub mod error; | |||||||
| mod key; | mod key; | ||||||
| mod store; | mod store; | ||||||
|  |  | ||||||
|  | use crate::store::generate_key; | ||||||
| use std::collections::{HashMap, HashSet}; | use std::collections::{HashMap, HashSet}; | ||||||
| use std::path::Path; | use std::path::Path; | ||||||
| use std::str::from_utf8; |  | ||||||
| use std::sync::Arc; | use std::sync::Arc; | ||||||
|  | use uuid::Uuid; | ||||||
|  |  | ||||||
| use serde::{Deserialize, Serialize}; | use serde::{Deserialize, Serialize}; | ||||||
| use serde_json::Value; | use serde_json::Value; | ||||||
| use sha2::{Digest, Sha256}; |  | ||||||
| use time::OffsetDateTime; | use time::OffsetDateTime; | ||||||
|  |  | ||||||
| pub use action::{actions, Action}; | pub use action::{actions, Action}; | ||||||
| @@ -42,62 +43,73 @@ impl AuthController { | |||||||
|  |  | ||||||
|     pub fn create_key(&self, value: Value) -> Result<Key> { |     pub fn create_key(&self, value: Value) -> Result<Key> { | ||||||
|         let key = Key::create_from_value(value)?; |         let key = Key::create_from_value(value)?; | ||||||
|         self.store.put_api_key(key) |         match self.store.get_api_key(key.uid)? { | ||||||
|  |             Some(_) => Err(AuthControllerError::ApiKeyAlreadyExists( | ||||||
|  |                 key.uid.to_string(), | ||||||
|  |             )), | ||||||
|  |             None => self.store.put_api_key(key), | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub fn update_key(&self, key: impl AsRef<str>, value: Value) -> Result<Key> { |     pub fn update_key(&self, uid: Uuid, value: Value) -> Result<Key> { | ||||||
|         let mut key = self.get_key(key)?; |         let mut key = self.get_key(uid)?; | ||||||
|         key.update_from_value(value)?; |         key.update_from_value(value)?; | ||||||
|         self.store.put_api_key(key) |         self.store.put_api_key(key) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub fn get_key(&self, key: impl AsRef<str>) -> Result<Key> { |     pub fn get_key(&self, uid: Uuid) -> Result<Key> { | ||||||
|         self.store |         self.store | ||||||
|             .get_api_key(&key)? |             .get_api_key(uid)? | ||||||
|             .ok_or_else(|| AuthControllerError::ApiKeyNotFound(key.as_ref().to_string())) |             .ok_or_else(|| AuthControllerError::ApiKeyNotFound(uid.to_string())) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub fn get_uid_from_sha(&self, key: &[u8]) -> Result<Option<Uuid>> { | ||||||
|  |         match &self.master_key { | ||||||
|  |             Some(master_key) => self.store.get_uid_from_sha(key, master_key.as_bytes()), | ||||||
|  |             None => Ok(None), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub fn try_get_uid_from_sha(&self, key: &str) -> Result<Uuid> { | ||||||
|  |         self.get_uid_from_sha(key.as_bytes())? | ||||||
|  |             .ok_or_else(|| AuthControllerError::ApiKeyNotFound(key.to_string())) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub fn get_key_filters( |     pub fn get_key_filters( | ||||||
|         &self, |         &self, | ||||||
|         key: impl AsRef<str>, |         uid: Uuid, | ||||||
|         search_rules: Option<SearchRules>, |         search_rules: Option<SearchRules>, | ||||||
|     ) -> Result<AuthFilter> { |     ) -> Result<AuthFilter> { | ||||||
|         let mut filters = AuthFilter::default(); |         let mut filters = AuthFilter::default(); | ||||||
|         if self |         let key = self | ||||||
|             .master_key |             .store | ||||||
|             .as_ref() |             .get_api_key(uid)? | ||||||
|             .map_or(false, |master_key| master_key != key.as_ref()) |             .ok_or_else(|| AuthControllerError::ApiKeyNotFound(uid.to_string()))?; | ||||||
|         { |  | ||||||
|             let key = self |  | ||||||
|                 .store |  | ||||||
|                 .get_api_key(&key)? |  | ||||||
|                 .ok_or_else(|| AuthControllerError::ApiKeyNotFound(key.as_ref().to_string()))?; |  | ||||||
|  |  | ||||||
|             if !key.indexes.iter().any(|i| i.as_str() == "*") { |         if !key.indexes.iter().any(|i| i.as_str() == "*") { | ||||||
|                 filters.search_rules = match search_rules { |             filters.search_rules = match search_rules { | ||||||
|                     // Intersect search_rules with parent key authorized indexes. |                 // Intersect search_rules with parent key authorized indexes. | ||||||
|                     Some(search_rules) => SearchRules::Map( |                 Some(search_rules) => SearchRules::Map( | ||||||
|                         key.indexes |                     key.indexes | ||||||
|                             .into_iter() |                         .into_iter() | ||||||
|                             .filter_map(|index| { |                         .filter_map(|index| { | ||||||
|                                 search_rules |                             search_rules | ||||||
|                                     .get_index_search_rules(&index) |                                 .get_index_search_rules(&index) | ||||||
|                                     .map(|index_search_rules| (index, Some(index_search_rules))) |                                 .map(|index_search_rules| (index, Some(index_search_rules))) | ||||||
|                             }) |                         }) | ||||||
|                             .collect(), |                         .collect(), | ||||||
|                     ), |                 ), | ||||||
|                     None => SearchRules::Set(key.indexes.into_iter().collect()), |                 None => SearchRules::Set(key.indexes.into_iter().collect()), | ||||||
|                 }; |             }; | ||||||
|             } else if let Some(search_rules) = search_rules { |         } else if let Some(search_rules) = search_rules { | ||||||
|                 filters.search_rules = search_rules; |             filters.search_rules = search_rules; | ||||||
|             } |  | ||||||
|  |  | ||||||
|             filters.allow_index_creation = key |  | ||||||
|                 .actions |  | ||||||
|                 .iter() |  | ||||||
|                 .any(|&action| action == Action::IndexesAdd || action == Action::All); |  | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         filters.allow_index_creation = key | ||||||
|  |             .actions | ||||||
|  |             .iter() | ||||||
|  |             .any(|&action| action == Action::IndexesAdd || action == Action::All); | ||||||
|  |  | ||||||
|         Ok(filters) |         Ok(filters) | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -105,13 +117,11 @@ impl AuthController { | |||||||
|         self.store.list_api_keys() |         self.store.list_api_keys() | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub fn delete_key(&self, key: impl AsRef<str>) -> Result<()> { |     pub fn delete_key(&self, uid: Uuid) -> Result<()> { | ||||||
|         if self.store.delete_api_key(&key)? { |         if self.store.delete_api_key(uid)? { | ||||||
|             Ok(()) |             Ok(()) | ||||||
|         } else { |         } else { | ||||||
|             Err(AuthControllerError::ApiKeyNotFound( |             Err(AuthControllerError::ApiKeyNotFound(uid.to_string())) | ||||||
|                 key.as_ref().to_string(), |  | ||||||
|             )) |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -121,32 +131,32 @@ impl AuthController { | |||||||
|  |  | ||||||
|     /// Generate a valid key from a key id using the current master key. |     /// Generate a valid key from a key id using the current master key. | ||||||
|     /// Returns None if no master key has been set. |     /// Returns None if no master key has been set. | ||||||
|     pub fn generate_key(&self, id: &str) -> Option<String> { |     pub fn generate_key(&self, uid: Uuid) -> Option<String> { | ||||||
|         self.master_key |         self.master_key | ||||||
|             .as_ref() |             .as_ref() | ||||||
|             .map(|master_key| generate_key(master_key.as_bytes(), id)) |             .map(|master_key| generate_key(uid.as_bytes(), master_key.as_bytes())) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// Check if the provided key is authorized to make a specific action |     /// Check if the provided key is authorized to make a specific action | ||||||
|     /// without checking if the key is valid. |     /// without checking if the key is valid. | ||||||
|     pub fn is_key_authorized( |     pub fn is_key_authorized( | ||||||
|         &self, |         &self, | ||||||
|         key: &[u8], |         uid: Uuid, | ||||||
|         action: Action, |         action: Action, | ||||||
|         index: Option<&str>, |         index: Option<&str>, | ||||||
|     ) -> Result<bool> { |     ) -> Result<bool> { | ||||||
|         match self |         match self | ||||||
|             .store |             .store | ||||||
|             // check if the key has access to all indexes. |             // check if the key has access to all indexes. | ||||||
|             .get_expiration_date(key, action, None)? |             .get_expiration_date(uid, action, None)? | ||||||
|             .or(match index { |             .or(match index { | ||||||
|                 // else check if the key has access to the requested index. |                 // else check if the key has access to the requested index. | ||||||
|                 Some(index) => { |                 Some(index) => { | ||||||
|                     self.store |                     self.store | ||||||
|                         .get_expiration_date(key, action, Some(index.as_bytes()))? |                         .get_expiration_date(uid, action, Some(index.as_bytes()))? | ||||||
|                 } |                 } | ||||||
|                 // or to any index if no index has been requested. |                 // or to any index if no index has been requested. | ||||||
|                 None => self.store.prefix_first_expiration_date(key, action)?, |                 None => self.store.prefix_first_expiration_date(uid, action)?, | ||||||
|             }) { |             }) { | ||||||
|             // check expiration date. |             // check expiration date. | ||||||
|             Some(Some(exp)) => Ok(OffsetDateTime::now_utc() < exp), |             Some(Some(exp)) => Ok(OffsetDateTime::now_utc() < exp), | ||||||
| @@ -156,29 +166,6 @@ impl AuthController { | |||||||
|             None => Ok(false), |             None => Ok(false), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// Check if the provided key is valid |  | ||||||
|     /// without checking if the key is authorized to make a specific action. |  | ||||||
|     pub fn is_key_valid(&self, key: &[u8]) -> Result<bool> { |  | ||||||
|         if let Some(id) = self.store.get_key_id(key) { |  | ||||||
|             let id = from_utf8(&id)?; |  | ||||||
|             if let Some(generated) = self.generate_key(id) { |  | ||||||
|                 return Ok(generated.as_bytes() == key); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         Ok(false) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /// Check if the provided key is valid |  | ||||||
|     /// and is authorized to make a specific action. |  | ||||||
|     pub fn authenticate(&self, key: &[u8], action: Action, index: Option<&str>) -> Result<bool> { |  | ||||||
|         if self.is_key_authorized(key, action, index)? { |  | ||||||
|             self.is_key_valid(key) |  | ||||||
|         } else { |  | ||||||
|             Ok(false) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } | } | ||||||
|  |  | ||||||
| pub struct AuthFilter { | pub struct AuthFilter { | ||||||
| @@ -258,12 +245,6 @@ pub struct IndexSearchRules { | |||||||
|     pub filter: Option<serde_json::Value>, |     pub filter: Option<serde_json::Value>, | ||||||
| } | } | ||||||
|  |  | ||||||
| fn generate_key(master_key: &[u8], keyid: &str) -> String { |  | ||||||
|     let key = [keyid.as_bytes(), master_key].concat(); |  | ||||||
|     let sha = Sha256::digest(&key); |  | ||||||
|     format!("{}{:x}", keyid, sha) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| fn generate_default_keys(store: &HeedAuthStore) -> Result<()> { | fn generate_default_keys(store: &HeedAuthStore) -> Result<()> { | ||||||
|     store.put_api_key(Key::default_admin())?; |     store.put_api_key(Key::default_admin())?; | ||||||
|     store.put_api_key(Key::default_search())?; |     store.put_api_key(Key::default_search())?; | ||||||
|   | |||||||
| @@ -1,4 +1,3 @@ | |||||||
| use enum_iterator::IntoEnumIterator; |  | ||||||
| use std::borrow::Cow; | use std::borrow::Cow; | ||||||
| use std::cmp::Reverse; | use std::cmp::Reverse; | ||||||
| use std::convert::TryFrom; | use std::convert::TryFrom; | ||||||
| @@ -8,20 +7,22 @@ use std::path::Path; | |||||||
| use std::str; | use std::str; | ||||||
| use std::sync::Arc; | use std::sync::Arc; | ||||||
|  |  | ||||||
|  | use enum_iterator::IntoEnumIterator; | ||||||
| use milli::heed::types::{ByteSlice, DecodeIgnore, SerdeJson}; | use milli::heed::types::{ByteSlice, DecodeIgnore, SerdeJson}; | ||||||
| use milli::heed::{Database, Env, EnvOpenOptions, RwTxn}; | use milli::heed::{Database, Env, EnvOpenOptions, RwTxn}; | ||||||
|  | use sha2::{Digest, Sha256}; | ||||||
| use time::OffsetDateTime; | use time::OffsetDateTime; | ||||||
|  | use uuid::Uuid; | ||||||
|  |  | ||||||
| use super::error::Result; | use super::error::Result; | ||||||
| use super::{Action, Key}; | use super::{Action, Key}; | ||||||
|  |  | ||||||
| const AUTH_STORE_SIZE: usize = 1_073_741_824; //1GiB | const AUTH_STORE_SIZE: usize = 1_073_741_824; //1GiB | ||||||
| pub const KEY_ID_LENGTH: usize = 8; |  | ||||||
| const AUTH_DB_PATH: &str = "auth"; | const AUTH_DB_PATH: &str = "auth"; | ||||||
| const KEY_DB_NAME: &str = "api-keys"; | const KEY_DB_NAME: &str = "api-keys"; | ||||||
| const KEY_ID_ACTION_INDEX_EXPIRATION_DB_NAME: &str = "keyid-action-index-expiration"; | const KEY_ID_ACTION_INDEX_EXPIRATION_DB_NAME: &str = "keyid-action-index-expiration"; | ||||||
|  |  | ||||||
| pub type KeyId = [u8; KEY_ID_LENGTH]; | pub type KeyId = Uuid; | ||||||
|  |  | ||||||
| #[derive(Clone)] | #[derive(Clone)] | ||||||
| pub struct HeedAuthStore { | pub struct HeedAuthStore { | ||||||
| @@ -73,12 +74,13 @@ impl HeedAuthStore { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub fn put_api_key(&self, key: Key) -> Result<Key> { |     pub fn put_api_key(&self, key: Key) -> Result<Key> { | ||||||
|  |         let uid = key.uid; | ||||||
|         let mut wtxn = self.env.write_txn()?; |         let mut wtxn = self.env.write_txn()?; | ||||||
|         self.keys.put(&mut wtxn, &key.id, &key)?; |  | ||||||
|  |  | ||||||
|         let id = key.id; |         self.keys.put(&mut wtxn, uid.as_bytes(), &key)?; | ||||||
|  |  | ||||||
|         // delete key from inverted database before refilling it. |         // delete key from inverted database before refilling it. | ||||||
|         self.delete_key_from_inverted_db(&mut wtxn, &id)?; |         self.delete_key_from_inverted_db(&mut wtxn, &uid)?; | ||||||
|         // create inverted database. |         // create inverted database. | ||||||
|         let db = self.action_keyid_index_expiration; |         let db = self.action_keyid_index_expiration; | ||||||
|  |  | ||||||
| @@ -93,13 +95,13 @@ impl HeedAuthStore { | |||||||
|         for action in actions { |         for action in actions { | ||||||
|             if no_index_restriction { |             if no_index_restriction { | ||||||
|                 // If there is no index restriction we put None. |                 // If there is no index restriction we put None. | ||||||
|                 db.put(&mut wtxn, &(&id, &action, None), &key.expires_at)?; |                 db.put(&mut wtxn, &(&uid, &action, None), &key.expires_at)?; | ||||||
|             } else { |             } else { | ||||||
|                 // else we create a key for each index. |                 // else we create a key for each index. | ||||||
|                 for index in key.indexes.iter() { |                 for index in key.indexes.iter() { | ||||||
|                     db.put( |                     db.put( | ||||||
|                         &mut wtxn, |                         &mut wtxn, | ||||||
|                         &(&id, &action, Some(index.as_bytes())), |                         &(&uid, &action, Some(index.as_bytes())), | ||||||
|                         &key.expires_at, |                         &key.expires_at, | ||||||
|                     )?; |                     )?; | ||||||
|                 } |                 } | ||||||
| @@ -111,24 +113,33 @@ impl HeedAuthStore { | |||||||
|         Ok(key) |         Ok(key) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub fn get_api_key(&self, key: impl AsRef<str>) -> Result<Option<Key>> { |     pub fn get_api_key(&self, uid: Uuid) -> Result<Option<Key>> { | ||||||
|         let rtxn = self.env.read_txn()?; |         let rtxn = self.env.read_txn()?; | ||||||
|         match self.get_key_id(key.as_ref().as_bytes()) { |         self.keys.get(&rtxn, uid.as_bytes()).map_err(|e| e.into()) | ||||||
|             Some(id) => self.keys.get(&rtxn, &id).map_err(|e| e.into()), |  | ||||||
|             None => Ok(None), |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub fn delete_api_key(&self, key: impl AsRef<str>) -> Result<bool> { |     pub fn get_uid_from_sha(&self, key_sha: &[u8], master_key: &[u8]) -> Result<Option<Uuid>> { | ||||||
|  |         let rtxn = self.env.read_txn()?; | ||||||
|  |         let uid = self | ||||||
|  |             .keys | ||||||
|  |             .remap_data_type::<DecodeIgnore>() | ||||||
|  |             .iter(&rtxn)? | ||||||
|  |             .filter_map(|res| match res { | ||||||
|  |                 Ok((uid, _)) if generate_key(uid, master_key).as_bytes() == key_sha => { | ||||||
|  |                     let (uid, _) = try_split_array_at(uid)?; | ||||||
|  |                     Some(Uuid::from_bytes(*uid)) | ||||||
|  |                 } | ||||||
|  |                 _ => None, | ||||||
|  |             }) | ||||||
|  |             .next(); | ||||||
|  |  | ||||||
|  |         Ok(uid) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub fn delete_api_key(&self, uid: Uuid) -> Result<bool> { | ||||||
|         let mut wtxn = self.env.write_txn()?; |         let mut wtxn = self.env.write_txn()?; | ||||||
|         let existing = match self.get_key_id(key.as_ref().as_bytes()) { |         let existing = self.keys.delete(&mut wtxn, uid.as_bytes())?; | ||||||
|             Some(id) => { |         self.delete_key_from_inverted_db(&mut wtxn, &uid)?; | ||||||
|                 let existing = self.keys.delete(&mut wtxn, &id)?; |  | ||||||
|                 self.delete_key_from_inverted_db(&mut wtxn, &id)?; |  | ||||||
|                 existing |  | ||||||
|             } |  | ||||||
|             None => false, |  | ||||||
|         }; |  | ||||||
|         wtxn.commit()?; |         wtxn.commit()?; | ||||||
|  |  | ||||||
|         Ok(existing) |         Ok(existing) | ||||||
| @@ -147,49 +158,37 @@ impl HeedAuthStore { | |||||||
|  |  | ||||||
|     pub fn get_expiration_date( |     pub fn get_expiration_date( | ||||||
|         &self, |         &self, | ||||||
|         key: &[u8], |         uid: Uuid, | ||||||
|         action: Action, |         action: Action, | ||||||
|         index: Option<&[u8]>, |         index: Option<&[u8]>, | ||||||
|     ) -> Result<Option<Option<OffsetDateTime>>> { |     ) -> Result<Option<Option<OffsetDateTime>>> { | ||||||
|         let rtxn = self.env.read_txn()?; |         let rtxn = self.env.read_txn()?; | ||||||
|         match self.get_key_id(key) { |         let tuple = (&uid, &action, index); | ||||||
|             Some(id) => { |         Ok(self.action_keyid_index_expiration.get(&rtxn, &tuple)?) | ||||||
|                 let tuple = (&id, &action, index); |  | ||||||
|                 Ok(self.action_keyid_index_expiration.get(&rtxn, &tuple)?) |  | ||||||
|             } |  | ||||||
|             None => Ok(None), |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub fn prefix_first_expiration_date( |     pub fn prefix_first_expiration_date( | ||||||
|         &self, |         &self, | ||||||
|         key: &[u8], |         uid: Uuid, | ||||||
|         action: Action, |         action: Action, | ||||||
|     ) -> Result<Option<Option<OffsetDateTime>>> { |     ) -> Result<Option<Option<OffsetDateTime>>> { | ||||||
|         let rtxn = self.env.read_txn()?; |         let rtxn = self.env.read_txn()?; | ||||||
|         match self.get_key_id(key) { |         let tuple = (&uid, &action, None); | ||||||
|             Some(id) => { |         let exp = self | ||||||
|                 let tuple = (&id, &action, None); |             .action_keyid_index_expiration | ||||||
|                 Ok(self |             .prefix_iter(&rtxn, &tuple)? | ||||||
|                     .action_keyid_index_expiration |             .next() | ||||||
|                     .prefix_iter(&rtxn, &tuple)? |             .transpose()? | ||||||
|                     .next() |             .map(|(_, expiration)| expiration); | ||||||
|                     .transpose()? |  | ||||||
|                     .map(|(_, expiration)| expiration)) |  | ||||||
|             } |  | ||||||
|             None => Ok(None), |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     pub fn get_key_id(&self, key: &[u8]) -> Option<KeyId> { |         Ok(exp) | ||||||
|         try_split_array_at::<_, KEY_ID_LENGTH>(key).map(|(id, _)| *id) |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn delete_key_from_inverted_db(&self, wtxn: &mut RwTxn, key: &KeyId) -> Result<()> { |     fn delete_key_from_inverted_db(&self, wtxn: &mut RwTxn, key: &KeyId) -> Result<()> { | ||||||
|         let mut iter = self |         let mut iter = self | ||||||
|             .action_keyid_index_expiration |             .action_keyid_index_expiration | ||||||
|             .remap_types::<ByteSlice, DecodeIgnore>() |             .remap_types::<ByteSlice, DecodeIgnore>() | ||||||
|             .prefix_iter_mut(wtxn, key)?; |             .prefix_iter_mut(wtxn, key.as_bytes())?; | ||||||
|         while iter.next().transpose()?.is_some() { |         while iter.next().transpose()?.is_some() { | ||||||
|             // safety: we don't keep references from inside the LMDB database. |             // safety: we don't keep references from inside the LMDB database. | ||||||
|             unsafe { iter.del_current()? }; |             unsafe { iter.del_current()? }; | ||||||
| @@ -207,14 +206,15 @@ impl<'a> milli::heed::BytesDecode<'a> for KeyIdActionCodec { | |||||||
|     type DItem = (KeyId, Action, Option<&'a [u8]>); |     type DItem = (KeyId, Action, Option<&'a [u8]>); | ||||||
|  |  | ||||||
|     fn bytes_decode(bytes: &'a [u8]) -> Option<Self::DItem> { |     fn bytes_decode(bytes: &'a [u8]) -> Option<Self::DItem> { | ||||||
|         let (key_id, action_bytes) = try_split_array_at(bytes)?; |         let (key_id_bytes, action_bytes) = try_split_array_at(bytes)?; | ||||||
|         let (action_bytes, index) = match try_split_array_at(action_bytes)? { |         let (action_bytes, index) = match try_split_array_at(action_bytes)? { | ||||||
|             (action, []) => (action, None), |             (action, []) => (action, None), | ||||||
|             (action, index) => (action, Some(index)), |             (action, index) => (action, Some(index)), | ||||||
|         }; |         }; | ||||||
|  |         let key_id = Uuid::from_bytes(*key_id_bytes); | ||||||
|         let action = Action::from_repr(u8::from_be_bytes(*action_bytes))?; |         let action = Action::from_repr(u8::from_be_bytes(*action_bytes))?; | ||||||
|  |  | ||||||
|         Some((*key_id, action, index)) |         Some((key_id, action, index)) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -224,7 +224,7 @@ impl<'a> milli::heed::BytesEncode<'a> for KeyIdActionCodec { | |||||||
|     fn bytes_encode((key_id, action, index): &Self::EItem) -> Option<Cow<[u8]>> { |     fn bytes_encode((key_id, action, index): &Self::EItem) -> Option<Cow<[u8]>> { | ||||||
|         let mut bytes = Vec::new(); |         let mut bytes = Vec::new(); | ||||||
|  |  | ||||||
|         bytes.extend_from_slice(*key_id); |         bytes.extend_from_slice(key_id.as_bytes()); | ||||||
|         let action_bytes = u8::to_be_bytes(action.repr()); |         let action_bytes = u8::to_be_bytes(action.repr()); | ||||||
|         bytes.extend_from_slice(&action_bytes); |         bytes.extend_from_slice(&action_bytes); | ||||||
|         if let Some(index) = index { |         if let Some(index) = index { | ||||||
| @@ -235,6 +235,12 @@ impl<'a> milli::heed::BytesEncode<'a> for KeyIdActionCodec { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | pub fn generate_key(uid: &[u8], master_key: &[u8]) -> String { | ||||||
|  |     let key = [uid, master_key].concat(); | ||||||
|  |     let sha = Sha256::digest(&key); | ||||||
|  |     format!("{:x}", sha) | ||||||
|  | } | ||||||
|  |  | ||||||
| /// Divides one slice into two at an index, returns `None` if mid is out of bounds. | /// Divides one slice into two at an index, returns `None` if mid is out of bounds. | ||||||
| pub fn try_split_at<T>(slice: &[T], mid: usize) -> Option<(&[T], &[T])> { | pub fn try_split_at<T>(slice: &[T], mid: usize) -> Option<(&[T], &[T])> { | ||||||
|     if mid <= slice.len() { |     if mid <= slice.len() { | ||||||
|   | |||||||
| @@ -166,6 +166,9 @@ pub enum Code { | |||||||
|     InvalidApiKeyIndexes, |     InvalidApiKeyIndexes, | ||||||
|     InvalidApiKeyExpiresAt, |     InvalidApiKeyExpiresAt, | ||||||
|     InvalidApiKeyDescription, |     InvalidApiKeyDescription, | ||||||
|  |     InvalidApiKeyName, | ||||||
|  |     InvalidApiKeyUid, | ||||||
|  |     ApiKeyAlreadyExists, | ||||||
| } | } | ||||||
|  |  | ||||||
| impl Code { | impl Code { | ||||||
| @@ -272,6 +275,9 @@ impl Code { | |||||||
|             InvalidApiKeyDescription => { |             InvalidApiKeyDescription => { | ||||||
|                 ErrCode::invalid("invalid_api_key_description", StatusCode::BAD_REQUEST) |                 ErrCode::invalid("invalid_api_key_description", StatusCode::BAD_REQUEST) | ||||||
|             } |             } | ||||||
|  |             InvalidApiKeyName => ErrCode::invalid("invalid_api_key_name", StatusCode::BAD_REQUEST), | ||||||
|  |             InvalidApiKeyUid => ErrCode::invalid("invalid_api_key_uid", StatusCode::BAD_REQUEST), | ||||||
|  |             ApiKeyAlreadyExists => ErrCode::invalid("api_key_already_exists", StatusCode::CONFLICT), | ||||||
|             InvalidMinWordLengthForTypo => { |             InvalidMinWordLengthForTypo => { | ||||||
|                 ErrCode::invalid("invalid_min_word_length_for_typo", StatusCode::BAD_REQUEST) |                 ErrCode::invalid("invalid_min_word_length_for_typo", StatusCode::BAD_REQUEST) | ||||||
|             } |             } | ||||||
|   | |||||||
| @@ -132,6 +132,7 @@ pub mod policies { | |||||||
|     use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation}; |     use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation}; | ||||||
|     use serde::{Deserialize, Serialize}; |     use serde::{Deserialize, Serialize}; | ||||||
|     use time::OffsetDateTime; |     use time::OffsetDateTime; | ||||||
|  |     use uuid::Uuid; | ||||||
|  |  | ||||||
|     use crate::extractors::authentication::Policy; |     use crate::extractors::authentication::Policy; | ||||||
|     use meilisearch_auth::{Action, AuthController, AuthFilter, SearchRules}; |     use meilisearch_auth::{Action, AuthController, AuthFilter, SearchRules}; | ||||||
| @@ -146,16 +147,16 @@ pub mod policies { | |||||||
|         validation |         validation | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// Extracts the key prefix used to sign the payload from the payload, without performing any validation. |     /// Extracts the key id used to sign the payload from the payload, without performing any validation. | ||||||
|     fn extract_key_prefix(token: &str) -> Option<String> { |     fn extract_key_id(token: &str) -> Option<Uuid> { | ||||||
|         let mut validation = tenant_token_validation(); |         let mut validation = tenant_token_validation(); | ||||||
|         validation.insecure_disable_signature_validation(); |         validation.insecure_disable_signature_validation(); | ||||||
|         let dummy_key = DecodingKey::from_secret(b"secret"); |         let dummy_key = DecodingKey::from_secret(b"secret"); | ||||||
|         let token_data = decode::<Claims>(token, &dummy_key, &validation).ok()?; |         let token_data = decode::<Claims>(token, &dummy_key, &validation).ok()?; | ||||||
|  |  | ||||||
|         // get token fields without validating it. |         // get token fields without validating it. | ||||||
|         let Claims { api_key_prefix, .. } = token_data.claims; |         let Claims { uid, .. } = token_data.claims; | ||||||
|         Some(api_key_prefix) |         Some(uid) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub struct MasterPolicy; |     pub struct MasterPolicy; | ||||||
| @@ -195,8 +196,10 @@ pub mod policies { | |||||||
|                 return Some(filters); |                 return Some(filters); | ||||||
|             } else if let Some(action) = Action::from_repr(A) { |             } else if let Some(action) = Action::from_repr(A) { | ||||||
|                 // API key |                 // API key | ||||||
|                 if let Ok(true) = auth.authenticate(token.as_bytes(), action, index) { |                 if let Ok(Some(uid)) = auth.get_uid_from_sha(token.as_bytes()) { | ||||||
|                     return auth.get_key_filters(token, None).ok(); |                     if let Ok(true) = auth.is_key_authorized(uid, action, index) { | ||||||
|  |                         return auth.get_key_filters(uid, None).ok(); | ||||||
|  |                     } | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  |  | ||||||
| @@ -215,14 +218,11 @@ pub mod policies { | |||||||
|                 return None; |                 return None; | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             let api_key_prefix = extract_key_prefix(token)?; |             let uid = extract_key_id(token)?; | ||||||
|             // check if parent key is authorized to do the action. |             // check if parent key is authorized to do the action. | ||||||
|             if auth |             if auth.is_key_authorized(uid, Action::Search, index).ok()? { | ||||||
|                 .is_key_authorized(api_key_prefix.as_bytes(), Action::Search, index) |  | ||||||
|                 .ok()? |  | ||||||
|             { |  | ||||||
|                 // Check if tenant token is valid. |                 // Check if tenant token is valid. | ||||||
|                 let key = auth.generate_key(&api_key_prefix)?; |                 let key = auth.generate_key(uid)?; | ||||||
|                 let data = decode::<Claims>( |                 let data = decode::<Claims>( | ||||||
|                     token, |                     token, | ||||||
|                     &DecodingKey::from_secret(key.as_bytes()), |                     &DecodingKey::from_secret(key.as_bytes()), | ||||||
| @@ -245,7 +245,7 @@ pub mod policies { | |||||||
|                 } |                 } | ||||||
|  |  | ||||||
|                 return auth |                 return auth | ||||||
|                     .get_key_filters(api_key_prefix, Some(data.claims.search_rules)) |                     .get_key_filters(uid, Some(data.claims.search_rules)) | ||||||
|                     .ok(); |                     .ok(); | ||||||
|             } |             } | ||||||
|  |  | ||||||
| @@ -258,6 +258,6 @@ pub mod policies { | |||||||
|     struct Claims { |     struct Claims { | ||||||
|         search_rules: SearchRules, |         search_rules: SearchRules, | ||||||
|         exp: Option<i64>, |         exp: Option<i64>, | ||||||
|         api_key_prefix: String, |         uid: Uuid, | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,4 +1,5 @@ | |||||||
| use std::str; | use std::str; | ||||||
|  | use uuid::Uuid; | ||||||
|  |  | ||||||
| use actix_web::{web, HttpRequest, HttpResponse}; | use actix_web::{web, HttpRequest, HttpResponse}; | ||||||
|  |  | ||||||
| @@ -20,7 +21,7 @@ pub fn configure(cfg: &mut web::ServiceConfig) { | |||||||
|             .route(web::get().to(SeqHandler(list_api_keys))), |             .route(web::get().to(SeqHandler(list_api_keys))), | ||||||
|     ) |     ) | ||||||
|     .service( |     .service( | ||||||
|         web::resource("/{api_key}") |         web::resource("/{key}") | ||||||
|             .route(web::get().to(SeqHandler(get_api_key))) |             .route(web::get().to(SeqHandler(get_api_key))) | ||||||
|             .route(web::patch().to(SeqHandler(patch_api_key))) |             .route(web::patch().to(SeqHandler(patch_api_key))) | ||||||
|             .route(web::delete().to(SeqHandler(delete_api_key))), |             .route(web::delete().to(SeqHandler(delete_api_key))), | ||||||
| @@ -65,9 +66,12 @@ pub async fn get_api_key( | |||||||
|     auth_controller: GuardedData<MasterPolicy, AuthController>, |     auth_controller: GuardedData<MasterPolicy, AuthController>, | ||||||
|     path: web::Path<AuthParam>, |     path: web::Path<AuthParam>, | ||||||
| ) -> Result<HttpResponse, ResponseError> { | ) -> Result<HttpResponse, ResponseError> { | ||||||
|     let api_key = path.into_inner().api_key; |     let key = path.into_inner().key; | ||||||
|  |  | ||||||
|     let res = tokio::task::spawn_blocking(move || -> Result<_, AuthControllerError> { |     let res = tokio::task::spawn_blocking(move || -> Result<_, AuthControllerError> { | ||||||
|         let key = auth_controller.get_key(&api_key)?; |         let uid = Uuid::parse_str(&key).or_else(|_| auth_controller.try_get_uid_from_sha(&key))?; | ||||||
|  |         let key = auth_controller.get_key(uid)?; | ||||||
|  |  | ||||||
|         Ok(KeyView::from_key(key, &auth_controller)) |         Ok(KeyView::from_key(key, &auth_controller)) | ||||||
|     }) |     }) | ||||||
|     .await |     .await | ||||||
| @@ -81,10 +85,12 @@ pub async fn patch_api_key( | |||||||
|     body: web::Json<Value>, |     body: web::Json<Value>, | ||||||
|     path: web::Path<AuthParam>, |     path: web::Path<AuthParam>, | ||||||
| ) -> Result<HttpResponse, ResponseError> { | ) -> Result<HttpResponse, ResponseError> { | ||||||
|     let api_key = path.into_inner().api_key; |     let key = path.into_inner().key; | ||||||
|     let body = body.into_inner(); |     let body = body.into_inner(); | ||||||
|     let res = tokio::task::spawn_blocking(move || -> Result<_, AuthControllerError> { |     let res = tokio::task::spawn_blocking(move || -> Result<_, AuthControllerError> { | ||||||
|         let key = auth_controller.update_key(&api_key, body)?; |         let uid = Uuid::parse_str(&key).or_else(|_| auth_controller.try_get_uid_from_sha(&key))?; | ||||||
|  |         let key = auth_controller.update_key(uid, body)?; | ||||||
|  |  | ||||||
|         Ok(KeyView::from_key(key, &auth_controller)) |         Ok(KeyView::from_key(key, &auth_controller)) | ||||||
|     }) |     }) | ||||||
|     .await |     .await | ||||||
| @@ -97,24 +103,29 @@ pub async fn delete_api_key( | |||||||
|     auth_controller: GuardedData<MasterPolicy, AuthController>, |     auth_controller: GuardedData<MasterPolicy, AuthController>, | ||||||
|     path: web::Path<AuthParam>, |     path: web::Path<AuthParam>, | ||||||
| ) -> Result<HttpResponse, ResponseError> { | ) -> Result<HttpResponse, ResponseError> { | ||||||
|     let api_key = path.into_inner().api_key; |     let key = path.into_inner().key; | ||||||
|     tokio::task::spawn_blocking(move || auth_controller.delete_key(&api_key)) |     tokio::task::spawn_blocking(move || { | ||||||
|         .await |         let uid = Uuid::parse_str(&key).or_else(|_| auth_controller.try_get_uid_from_sha(&key))?; | ||||||
|         .map_err(|e| ResponseError::from_msg(e.to_string(), Code::Internal))??; |         auth_controller.delete_key(uid) | ||||||
|  |     }) | ||||||
|  |     .await | ||||||
|  |     .map_err(|e| ResponseError::from_msg(e.to_string(), Code::Internal))??; | ||||||
|  |  | ||||||
|     Ok(HttpResponse::NoContent().finish()) |     Ok(HttpResponse::NoContent().finish()) | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Deserialize)] | #[derive(Deserialize)] | ||||||
| pub struct AuthParam { | pub struct AuthParam { | ||||||
|     api_key: String, |     key: String, | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Debug, Serialize)] | #[derive(Debug, Serialize)] | ||||||
| #[serde(rename_all = "camelCase")] | #[serde(rename_all = "camelCase")] | ||||||
| struct KeyView { | struct KeyView { | ||||||
|  |     name: Option<String>, | ||||||
|     description: Option<String>, |     description: Option<String>, | ||||||
|     key: String, |     key: String, | ||||||
|  |     uid: Uuid, | ||||||
|     actions: Vec<Action>, |     actions: Vec<Action>, | ||||||
|     indexes: Vec<String>, |     indexes: Vec<String>, | ||||||
|     #[serde(serialize_with = "time::serde::rfc3339::option::serialize")] |     #[serde(serialize_with = "time::serde::rfc3339::option::serialize")] | ||||||
| @@ -127,12 +138,13 @@ struct KeyView { | |||||||
|  |  | ||||||
| impl KeyView { | impl KeyView { | ||||||
|     fn from_key(key: Key, auth: &AuthController) -> Self { |     fn from_key(key: Key, auth: &AuthController) -> Self { | ||||||
|         let key_id = str::from_utf8(&key.id).unwrap(); |         let generated_key = auth.generate_key(key.uid).unwrap_or_default(); | ||||||
|         let generated_key = auth.generate_key(key_id).unwrap_or_default(); |  | ||||||
|  |  | ||||||
|         KeyView { |         KeyView { | ||||||
|  |             name: key.name, | ||||||
|             description: key.description, |             description: key.description, | ||||||
|             key: generated_key, |             key: generated_key, | ||||||
|  |             uid: key.uid, | ||||||
|             actions: key.actions, |             actions: key.actions, | ||||||
|             indexes: key.indexes, |             indexes: key.indexes, | ||||||
|             expires_at: key.expires_at, |             expires_at: key.expires_at, | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user