From 96a5791e395829c8770f3519fee3ba0e3b988d20 Mon Sep 17 00:00:00 2001 From: ManyTheFish Date: Wed, 25 May 2022 10:32:47 +0200 Subject: [PATCH] Add uid and name fields in keys --- Cargo.lock | 1 + meilisearch-auth/Cargo.toml | 1 + meilisearch-auth/src/error.rs | 11 ++ meilisearch-auth/src/key.rs | 50 +++--- meilisearch-auth/src/lib.rs | 143 ++++++++---------- meilisearch-auth/src/store.rs | 106 +++++++------ meilisearch-error/src/lib.rs | 6 + .../src/extractors/authentication/mod.rs | 28 ++-- meilisearch-http/src/routes/api_key.rs | 36 +++-- 9 files changed, 205 insertions(+), 177 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 39eb78987..f48e6c59d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1982,6 +1982,7 @@ dependencies = [ "sha2", "thiserror", "time 0.3.9", + "uuid", ] [[package]] diff --git a/meilisearch-auth/Cargo.toml b/meilisearch-auth/Cargo.toml index dd12b5b63..9a7ce0d3e 100644 --- a/meilisearch-auth/Cargo.toml +++ b/meilisearch-auth/Cargo.toml @@ -13,3 +13,4 @@ serde_json = { version = "1.0.79", features = ["preserve_order"] } sha2 = "0.10.2" thiserror = "1.0.30" time = { version = "0.3.7", features = ["serde-well-known", "formatting", "parsing", "macros"] } +uuid = { version = "0.8.2", features = ["serde"] } diff --git a/meilisearch-auth/src/error.rs b/meilisearch-auth/src/error.rs index 8a87eda27..dc6301348 100644 --- a/meilisearch-auth/src/error.rs +++ b/meilisearch-auth/src/error.rs @@ -18,8 +18,16 @@ pub enum AuthControllerError { InvalidApiKeyExpiresAt(Value), #[error("`description` field value `{0}` is invalid. It should be a string or specified as a null 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.")] ApiKeyNotFound(String), + #[error("`uid` field value `{0}` already exists for an API key.")] + ApiKeyAlreadyExists(String), #[error("Internal error: {0}")] Internal(Box), } @@ -39,7 +47,10 @@ impl ErrorCode for AuthControllerError { Self::InvalidApiKeyIndexes(_) => Code::InvalidApiKeyIndexes, Self::InvalidApiKeyExpiresAt(_) => Code::InvalidApiKeyExpiresAt, Self::InvalidApiKeyDescription(_) => Code::InvalidApiKeyDescription, + Self::InvalidApiKeyName(_) => Code::InvalidApiKeyName, Self::ApiKeyNotFound(_) => Code::ApiKeyNotFound, + Self::InvalidApiKeyUid(_) => Code::InvalidApiKeyUid, + Self::ApiKeyAlreadyExists(_) => Code::ApiKeyAlreadyExists, Self::Internal(_) => Code::Internal, } } diff --git a/meilisearch-auth/src/key.rs b/meilisearch-auth/src/key.rs index 1b06f34be..d69c0aed4 100644 --- a/meilisearch-auth/src/key.rs +++ b/meilisearch-auth/src/key.rs @@ -1,18 +1,21 @@ use crate::action::Action; use crate::error::{AuthControllerError, Result}; -use crate::store::{KeyId, KEY_ID_LENGTH}; -use rand::Rng; +use crate::store::KeyId; + use serde::{Deserialize, Serialize}; use serde_json::{from_value, Value}; use time::format_description::well_known::Rfc3339; use time::macros::{format_description, time}; use time::{Date, OffsetDateTime, PrimitiveDateTime}; +use uuid::Uuid; #[derive(Debug, Deserialize, Serialize)] pub struct Key { #[serde(skip_serializing_if = "Option::is_none")] pub description: Option, - pub id: KeyId, + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, + pub uid: KeyId, pub actions: Vec, pub indexes: Vec, #[serde(with = "time::serde::rfc3339::option")] @@ -25,6 +28,15 @@ pub struct Key { impl Key { pub fn create_from_value(value: Value) -> Result { + 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") { Some(Value::Null) => None, Some(des) => Some( @@ -34,7 +46,13 @@ impl Key { 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 .get("actions") @@ -61,8 +79,9 @@ impl Key { let updated_at = created_at; Ok(Self { + name, description, - id, + uid, actions, indexes, expires_at, @@ -101,9 +120,11 @@ impl Key { pub(crate) fn default_admin() -> Self { let now = OffsetDateTime::now_utc(); + let uid = Uuid::new_v4(); 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()), - id: generate_id(), + uid, actions: vec![Action::All], indexes: vec!["*".to_string()], expires_at: None, @@ -114,11 +135,13 @@ impl Key { pub(crate) fn default_search() -> Self { let now = OffsetDateTime::now_utc(); + let uid = Uuid::new_v4(); Self { + name: Some("search".to_string()), description: Some( "Default Search API Key (Use it to search from the frontend)".to_string(), ), - id: generate_id(), + uid, actions: vec![Action::Search], indexes: vec!["*".to_string()], 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> { match value { Value::String(string) => OffsetDateTime::parse(string, &Rfc3339) diff --git a/meilisearch-auth/src/lib.rs b/meilisearch-auth/src/lib.rs index 22263735e..9f9c59c35 100644 --- a/meilisearch-auth/src/lib.rs +++ b/meilisearch-auth/src/lib.rs @@ -4,14 +4,15 @@ pub mod error; mod key; mod store; +use crate::store::generate_key; use std::collections::{HashMap, HashSet}; use std::path::Path; -use std::str::from_utf8; + use std::sync::Arc; +use uuid::Uuid; use serde::{Deserialize, Serialize}; use serde_json::Value; -use sha2::{Digest, Sha256}; use time::OffsetDateTime; pub use action::{actions, Action}; @@ -42,62 +43,73 @@ impl AuthController { pub fn create_key(&self, value: Value) -> Result { 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, value: Value) -> Result { - let mut key = self.get_key(key)?; + pub fn update_key(&self, uid: Uuid, value: Value) -> Result { + let mut key = self.get_key(uid)?; key.update_from_value(value)?; self.store.put_api_key(key) } - pub fn get_key(&self, key: impl AsRef) -> Result { + pub fn get_key(&self, uid: Uuid) -> Result { self.store - .get_api_key(&key)? - .ok_or_else(|| AuthControllerError::ApiKeyNotFound(key.as_ref().to_string())) + .get_api_key(uid)? + .ok_or_else(|| AuthControllerError::ApiKeyNotFound(uid.to_string())) + } + + pub fn get_uid_from_sha(&self, key: &[u8]) -> Result> { + 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 { + self.get_uid_from_sha(key.as_bytes())? + .ok_or_else(|| AuthControllerError::ApiKeyNotFound(key.to_string())) } pub fn get_key_filters( &self, - key: impl AsRef, + uid: Uuid, search_rules: Option, ) -> Result { let mut filters = AuthFilter::default(); - if self - .master_key - .as_ref() - .map_or(false, |master_key| master_key != key.as_ref()) - { - let key = self - .store - .get_api_key(&key)? - .ok_or_else(|| AuthControllerError::ApiKeyNotFound(key.as_ref().to_string()))?; + let key = self + .store + .get_api_key(uid)? + .ok_or_else(|| AuthControllerError::ApiKeyNotFound(uid.to_string()))?; - if !key.indexes.iter().any(|i| i.as_str() == "*") { - filters.search_rules = match search_rules { - // Intersect search_rules with parent key authorized indexes. - Some(search_rules) => SearchRules::Map( - key.indexes - .into_iter() - .filter_map(|index| { - search_rules - .get_index_search_rules(&index) - .map(|index_search_rules| (index, Some(index_search_rules))) - }) - .collect(), - ), - None => SearchRules::Set(key.indexes.into_iter().collect()), - }; - } else if let Some(search_rules) = search_rules { - filters.search_rules = search_rules; - } - - filters.allow_index_creation = key - .actions - .iter() - .any(|&action| action == Action::IndexesAdd || action == Action::All); + if !key.indexes.iter().any(|i| i.as_str() == "*") { + filters.search_rules = match search_rules { + // Intersect search_rules with parent key authorized indexes. + Some(search_rules) => SearchRules::Map( + key.indexes + .into_iter() + .filter_map(|index| { + search_rules + .get_index_search_rules(&index) + .map(|index_search_rules| (index, Some(index_search_rules))) + }) + .collect(), + ), + None => SearchRules::Set(key.indexes.into_iter().collect()), + }; + } else if let Some(search_rules) = search_rules { + filters.search_rules = search_rules; } + filters.allow_index_creation = key + .actions + .iter() + .any(|&action| action == Action::IndexesAdd || action == Action::All); + Ok(filters) } @@ -105,13 +117,11 @@ impl AuthController { self.store.list_api_keys() } - pub fn delete_key(&self, key: impl AsRef) -> Result<()> { - if self.store.delete_api_key(&key)? { + pub fn delete_key(&self, uid: Uuid) -> Result<()> { + if self.store.delete_api_key(uid)? { Ok(()) } else { - Err(AuthControllerError::ApiKeyNotFound( - key.as_ref().to_string(), - )) + Err(AuthControllerError::ApiKeyNotFound(uid.to_string())) } } @@ -121,32 +131,32 @@ impl AuthController { /// Generate a valid key from a key id using the current master key. /// Returns None if no master key has been set. - pub fn generate_key(&self, id: &str) -> Option { + pub fn generate_key(&self, uid: Uuid) -> Option { self.master_key .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 /// without checking if the key is valid. pub fn is_key_authorized( &self, - key: &[u8], + uid: Uuid, action: Action, index: Option<&str>, ) -> Result { match self .store // check if the key has access to all indexes. - .get_expiration_date(key, action, None)? + .get_expiration_date(uid, action, None)? .or(match index { // else check if the key has access to the requested index. Some(index) => { 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. - None => self.store.prefix_first_expiration_date(key, action)?, + None => self.store.prefix_first_expiration_date(uid, action)?, }) { // check expiration date. Some(Some(exp)) => Ok(OffsetDateTime::now_utc() < exp), @@ -156,29 +166,6 @@ impl AuthController { 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 { - 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 { - if self.is_key_authorized(key, action, index)? { - self.is_key_valid(key) - } else { - Ok(false) - } - } } pub struct AuthFilter { @@ -258,12 +245,6 @@ pub struct IndexSearchRules { pub filter: Option, } -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<()> { store.put_api_key(Key::default_admin())?; store.put_api_key(Key::default_search())?; diff --git a/meilisearch-auth/src/store.rs b/meilisearch-auth/src/store.rs index 4bd3cdded..762e707bc 100644 --- a/meilisearch-auth/src/store.rs +++ b/meilisearch-auth/src/store.rs @@ -1,4 +1,3 @@ -use enum_iterator::IntoEnumIterator; use std::borrow::Cow; use std::cmp::Reverse; use std::convert::TryFrom; @@ -8,20 +7,22 @@ use std::path::Path; use std::str; use std::sync::Arc; +use enum_iterator::IntoEnumIterator; use milli::heed::types::{ByteSlice, DecodeIgnore, SerdeJson}; use milli::heed::{Database, Env, EnvOpenOptions, RwTxn}; +use sha2::{Digest, Sha256}; use time::OffsetDateTime; +use uuid::Uuid; use super::error::Result; use super::{Action, Key}; const AUTH_STORE_SIZE: usize = 1_073_741_824; //1GiB -pub const KEY_ID_LENGTH: usize = 8; const AUTH_DB_PATH: &str = "auth"; const KEY_DB_NAME: &str = "api-keys"; 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)] pub struct HeedAuthStore { @@ -73,12 +74,13 @@ impl HeedAuthStore { } pub fn put_api_key(&self, key: Key) -> Result { + let uid = key.uid; 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. - self.delete_key_from_inverted_db(&mut wtxn, &id)?; + self.delete_key_from_inverted_db(&mut wtxn, &uid)?; // create inverted database. let db = self.action_keyid_index_expiration; @@ -93,13 +95,13 @@ impl HeedAuthStore { for action in actions { if no_index_restriction { // 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 we create a key for each index. for index in key.indexes.iter() { db.put( &mut wtxn, - &(&id, &action, Some(index.as_bytes())), + &(&uid, &action, Some(index.as_bytes())), &key.expires_at, )?; } @@ -111,24 +113,33 @@ impl HeedAuthStore { Ok(key) } - pub fn get_api_key(&self, key: impl AsRef) -> Result> { + pub fn get_api_key(&self, uid: Uuid) -> Result> { let rtxn = self.env.read_txn()?; - match self.get_key_id(key.as_ref().as_bytes()) { - Some(id) => self.keys.get(&rtxn, &id).map_err(|e| e.into()), - None => Ok(None), - } + self.keys.get(&rtxn, uid.as_bytes()).map_err(|e| e.into()) } - pub fn delete_api_key(&self, key: impl AsRef) -> Result { + pub fn get_uid_from_sha(&self, key_sha: &[u8], master_key: &[u8]) -> Result> { + let rtxn = self.env.read_txn()?; + let uid = self + .keys + .remap_data_type::() + .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 { let mut wtxn = self.env.write_txn()?; - let existing = match self.get_key_id(key.as_ref().as_bytes()) { - Some(id) => { - let existing = self.keys.delete(&mut wtxn, &id)?; - self.delete_key_from_inverted_db(&mut wtxn, &id)?; - existing - } - None => false, - }; + let existing = self.keys.delete(&mut wtxn, uid.as_bytes())?; + self.delete_key_from_inverted_db(&mut wtxn, &uid)?; wtxn.commit()?; Ok(existing) @@ -147,49 +158,37 @@ impl HeedAuthStore { pub fn get_expiration_date( &self, - key: &[u8], + uid: Uuid, action: Action, index: Option<&[u8]>, ) -> Result>> { let rtxn = self.env.read_txn()?; - match self.get_key_id(key) { - Some(id) => { - let tuple = (&id, &action, index); - Ok(self.action_keyid_index_expiration.get(&rtxn, &tuple)?) - } - None => Ok(None), - } + let tuple = (&uid, &action, index); + Ok(self.action_keyid_index_expiration.get(&rtxn, &tuple)?) } pub fn prefix_first_expiration_date( &self, - key: &[u8], + uid: Uuid, action: Action, ) -> Result>> { let rtxn = self.env.read_txn()?; - match self.get_key_id(key) { - Some(id) => { - let tuple = (&id, &action, None); - Ok(self - .action_keyid_index_expiration - .prefix_iter(&rtxn, &tuple)? - .next() - .transpose()? - .map(|(_, expiration)| expiration)) - } - None => Ok(None), - } - } + let tuple = (&uid, &action, None); + let exp = self + .action_keyid_index_expiration + .prefix_iter(&rtxn, &tuple)? + .next() + .transpose()? + .map(|(_, expiration)| expiration); - pub fn get_key_id(&self, key: &[u8]) -> Option { - try_split_array_at::<_, KEY_ID_LENGTH>(key).map(|(id, _)| *id) + Ok(exp) } fn delete_key_from_inverted_db(&self, wtxn: &mut RwTxn, key: &KeyId) -> Result<()> { let mut iter = self .action_keyid_index_expiration .remap_types::() - .prefix_iter_mut(wtxn, key)?; + .prefix_iter_mut(wtxn, key.as_bytes())?; while iter.next().transpose()?.is_some() { // safety: we don't keep references from inside the LMDB database. unsafe { iter.del_current()? }; @@ -207,14 +206,15 @@ impl<'a> milli::heed::BytesDecode<'a> for KeyIdActionCodec { type DItem = (KeyId, Action, Option<&'a [u8]>); fn bytes_decode(bytes: &'a [u8]) -> Option { - 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)? { (action, []) => (action, None), (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))?; - 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> { 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()); bytes.extend_from_slice(&action_bytes); 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. pub fn try_split_at(slice: &[T], mid: usize) -> Option<(&[T], &[T])> { if mid <= slice.len() { diff --git a/meilisearch-error/src/lib.rs b/meilisearch-error/src/lib.rs index 11613497c..57882f8e0 100644 --- a/meilisearch-error/src/lib.rs +++ b/meilisearch-error/src/lib.rs @@ -166,6 +166,9 @@ pub enum Code { InvalidApiKeyIndexes, InvalidApiKeyExpiresAt, InvalidApiKeyDescription, + InvalidApiKeyName, + InvalidApiKeyUid, + ApiKeyAlreadyExists, } impl Code { @@ -272,6 +275,9 @@ impl Code { InvalidApiKeyDescription => { 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 => { ErrCode::invalid("invalid_min_word_length_for_typo", StatusCode::BAD_REQUEST) } diff --git a/meilisearch-http/src/extractors/authentication/mod.rs b/meilisearch-http/src/extractors/authentication/mod.rs index c4cd9ef14..cf93d363a 100644 --- a/meilisearch-http/src/extractors/authentication/mod.rs +++ b/meilisearch-http/src/extractors/authentication/mod.rs @@ -132,6 +132,7 @@ pub mod policies { use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation}; use serde::{Deserialize, Serialize}; use time::OffsetDateTime; + use uuid::Uuid; use crate::extractors::authentication::Policy; use meilisearch_auth::{Action, AuthController, AuthFilter, SearchRules}; @@ -146,16 +147,16 @@ pub mod policies { validation } - /// Extracts the key prefix used to sign the payload from the payload, without performing any validation. - fn extract_key_prefix(token: &str) -> Option { + /// Extracts the key id used to sign the payload from the payload, without performing any validation. + fn extract_key_id(token: &str) -> Option { let mut validation = tenant_token_validation(); validation.insecure_disable_signature_validation(); let dummy_key = DecodingKey::from_secret(b"secret"); let token_data = decode::(token, &dummy_key, &validation).ok()?; // get token fields without validating it. - let Claims { api_key_prefix, .. } = token_data.claims; - Some(api_key_prefix) + let Claims { uid, .. } = token_data.claims; + Some(uid) } pub struct MasterPolicy; @@ -195,8 +196,10 @@ pub mod policies { return Some(filters); } else if let Some(action) = Action::from_repr(A) { // API key - if let Ok(true) = auth.authenticate(token.as_bytes(), action, index) { - return auth.get_key_filters(token, None).ok(); + if let Ok(Some(uid)) = auth.get_uid_from_sha(token.as_bytes()) { + 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; } - let api_key_prefix = extract_key_prefix(token)?; + let uid = extract_key_id(token)?; // check if parent key is authorized to do the action. - if auth - .is_key_authorized(api_key_prefix.as_bytes(), Action::Search, index) - .ok()? - { + if auth.is_key_authorized(uid, Action::Search, index).ok()? { // Check if tenant token is valid. - let key = auth.generate_key(&api_key_prefix)?; + let key = auth.generate_key(uid)?; let data = decode::( token, &DecodingKey::from_secret(key.as_bytes()), @@ -245,7 +245,7 @@ pub mod policies { } return auth - .get_key_filters(api_key_prefix, Some(data.claims.search_rules)) + .get_key_filters(uid, Some(data.claims.search_rules)) .ok(); } @@ -258,6 +258,6 @@ pub mod policies { struct Claims { search_rules: SearchRules, exp: Option, - api_key_prefix: String, + uid: Uuid, } } diff --git a/meilisearch-http/src/routes/api_key.rs b/meilisearch-http/src/routes/api_key.rs index 310b09c4d..ba964e5d1 100644 --- a/meilisearch-http/src/routes/api_key.rs +++ b/meilisearch-http/src/routes/api_key.rs @@ -1,4 +1,5 @@ use std::str; +use uuid::Uuid; 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))), ) .service( - web::resource("/{api_key}") + web::resource("/{key}") .route(web::get().to(SeqHandler(get_api_key))) .route(web::patch().to(SeqHandler(patch_api_key))) .route(web::delete().to(SeqHandler(delete_api_key))), @@ -65,9 +66,12 @@ pub async fn get_api_key( auth_controller: GuardedData, path: web::Path, ) -> Result { - let api_key = path.into_inner().api_key; + let key = path.into_inner().key; + 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)) }) .await @@ -81,10 +85,12 @@ pub async fn patch_api_key( body: web::Json, path: web::Path, ) -> Result { - let api_key = path.into_inner().api_key; + let key = path.into_inner().key; let body = body.into_inner(); 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)) }) .await @@ -97,24 +103,29 @@ pub async fn delete_api_key( auth_controller: GuardedData, path: web::Path, ) -> Result { - let api_key = path.into_inner().api_key; - tokio::task::spawn_blocking(move || auth_controller.delete_key(&api_key)) - .await - .map_err(|e| ResponseError::from_msg(e.to_string(), Code::Internal))??; + let key = path.into_inner().key; + tokio::task::spawn_blocking(move || { + let uid = Uuid::parse_str(&key).or_else(|_| auth_controller.try_get_uid_from_sha(&key))?; + auth_controller.delete_key(uid) + }) + .await + .map_err(|e| ResponseError::from_msg(e.to_string(), Code::Internal))??; Ok(HttpResponse::NoContent().finish()) } #[derive(Deserialize)] pub struct AuthParam { - api_key: String, + key: String, } #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] struct KeyView { + name: Option, description: Option, key: String, + uid: Uuid, actions: Vec, indexes: Vec, #[serde(serialize_with = "time::serde::rfc3339::option::serialize")] @@ -127,12 +138,13 @@ struct KeyView { impl KeyView { fn from_key(key: Key, auth: &AuthController) -> Self { - let key_id = str::from_utf8(&key.id).unwrap(); - let generated_key = auth.generate_key(key_id).unwrap_or_default(); + let generated_key = auth.generate_key(key.uid).unwrap_or_default(); KeyView { + name: key.name, description: key.description, key: generated_key, + uid: key.uid, actions: key.actions, indexes: key.indexes, expires_at: key.expires_at,