From 00eb258a538ed39220c3dbb469f5c193f160cade Mon Sep 17 00:00:00 2001 From: Mubelotix Date: Thu, 19 Jun 2025 11:16:07 +0200 Subject: [PATCH 01/22] Fix comment --- crates/meilisearch-auth/src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/meilisearch-auth/src/lib.rs b/crates/meilisearch-auth/src/lib.rs index 27d163192..02d9201c5 100644 --- a/crates/meilisearch-auth/src/lib.rs +++ b/crates/meilisearch-auth/src/lib.rs @@ -158,7 +158,7 @@ impl AuthController { self.store.delete_all_keys() } - /// Delete all the keys in the DB. + /// Insert a key directly into the store. pub fn raw_insert_key(&mut self, key: Key) -> Result<()> { self.store.put_api_key(key)?; Ok(()) @@ -353,6 +353,7 @@ fn generate_default_keys(store: &HeedAuthStore) -> Result<()> { store.put_api_key(Key::default_chat())?; store.put_api_key(Key::default_admin())?; store.put_api_key(Key::default_search())?; + store.put_api_key(Key::default_management())?; Ok(()) } From b421c8e7deb13ca5bcfbf03ef33f958ffb8dbf32 Mon Sep 17 00:00:00 2001 From: Mubelotix Date: Thu, 19 Jun 2025 11:29:16 +0200 Subject: [PATCH 02/22] Add an AllRead key --- crates/meilisearch-auth/src/store.rs | 1 + crates/meilisearch-types/src/keys.rs | 48 ++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/crates/meilisearch-auth/src/store.rs b/crates/meilisearch-auth/src/store.rs index bae27afe4..6e4ff8389 100644 --- a/crates/meilisearch-auth/src/store.rs +++ b/crates/meilisearch-auth/src/store.rs @@ -89,6 +89,7 @@ impl HeedAuthStore { for action in &key.actions { match action { Action::All => actions.extend(enum_iterator::all::()), + Action::AllRead => actions.extend(enum_iterator::all::().filter(|a| a.is_read())), Action::DocumentsAll => { actions.extend( [Action::DocumentsGet, Action::DocumentsDelete, Action::DocumentsAdd] diff --git a/crates/meilisearch-types/src/keys.rs b/crates/meilisearch-types/src/keys.rs index df2810727..023e7e786 100644 --- a/crates/meilisearch-types/src/keys.rs +++ b/crates/meilisearch-types/src/keys.rs @@ -218,6 +218,9 @@ pub enum Action { #[serde(rename = "*")] #[deserr(rename = "*")] All = 0, + #[serde(rename = "*.read")] + #[deserr(rename = "*.read")] + AllRead, #[serde(rename = "search")] #[deserr(rename = "search")] Search, @@ -396,6 +399,51 @@ impl Action { } } + /// Whether the action should be included in [Action::AllRead]. + pub fn is_read(&self) -> bool { + use Action::*; + + // It's using an exhaustive match to force the addition of new actions. + match self { + // Any action that expands to others must return false, as it wouldn't be able to expand recursively. + All | AllRead | DocumentsAll | IndexesAll | ChatsAll | TasksAll | SettingsAll + | StatsAll | MetricsAll | DumpsAll | SnapshotsAll | ChatsSettingsAll => false, + + Search => true, + DocumentsAdd => false, + DocumentsGet => true, + DocumentsDelete => false, + IndexesAdd => false, + IndexesGet => true, + IndexesUpdate => false, + IndexesDelete => false, + IndexesSwap => false, + TasksCancel => false, + TasksDelete => false, + TasksGet => true, + SettingsGet => true, + SettingsUpdate => false, + StatsGet => true, + MetricsGet => true, + DumpsCreate => false, + SnapshotsCreate => false, + Version => true, + KeysAdd => false, + KeysGet => false, // Prevent privilege escalation by not allowing reading other keys. + KeysUpdate => false, + KeysDelete => false, + ExperimentalFeaturesGet => true, + ExperimentalFeaturesUpdate => false, + NetworkGet => true, + NetworkUpdate => false, + ChatCompletions => false, // Disabled because it might trigger generation of new chats. + ChatsGet => true, + ChatsDelete => false, + ChatsSettingsGet => true, + ChatsSettingsUpdate => false, + } + } + pub const fn repr(&self) -> u8 { *self as u8 } From 032b34c37716e6fd11981c7046d8a824a5e826a7 Mon Sep 17 00:00:00 2001 From: Mubelotix Date: Thu, 19 Jun 2025 11:29:32 +0200 Subject: [PATCH 03/22] Add a default management key --- crates/meilisearch-types/src/keys.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/crates/meilisearch-types/src/keys.rs b/crates/meilisearch-types/src/keys.rs index 023e7e786..e8db4014d 100644 --- a/crates/meilisearch-types/src/keys.rs +++ b/crates/meilisearch-types/src/keys.rs @@ -144,6 +144,21 @@ impl Key { } } + pub fn default_management() -> Self { + let now = OffsetDateTime::now_utc(); + let uid = Uuid::new_v4(); + Self { + name: Some("Default Management API Key".to_string()), + description: Some("Use it to peek into the instance in a read-only mode. Caution! Do not expose it on a public frontend".to_string()), + uid, + actions: vec![Action::AllRead], + indexes: vec![IndexUidPattern::all()], + expires_at: None, + created_at: now, + updated_at: now, + } + } + pub fn default_search() -> Self { let now = OffsetDateTime::now_utc(); let uid = Uuid::new_v4(); @@ -453,6 +468,7 @@ pub mod actions { use super::Action::*; pub(crate) const ALL: u8 = All.repr(); + pub const ALL_READ: u8 = AllRead.repr(); pub const SEARCH: u8 = Search.repr(); pub const DOCUMENTS_ALL: u8 = DocumentsAll.repr(); pub const DOCUMENTS_ADD: u8 = DocumentsAdd.repr(); From 11fedea788115448f6c6f7a854b340d65a1fd641 Mon Sep 17 00:00:00 2001 From: Mubelotix Date: Thu, 19 Jun 2025 11:42:45 +0200 Subject: [PATCH 04/22] Set static uuids to keys --- crates/meilisearch-types/src/keys.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/meilisearch-types/src/keys.rs b/crates/meilisearch-types/src/keys.rs index e8db4014d..4a3b58c20 100644 --- a/crates/meilisearch-types/src/keys.rs +++ b/crates/meilisearch-types/src/keys.rs @@ -131,7 +131,7 @@ pub struct Key { impl Key { pub fn default_admin() -> Self { let now = OffsetDateTime::now_utc(); - let uid = Uuid::new_v4(); + let uid = Uuid::from_u128(0); Self { name: Some("Default Admin API Key".to_string()), description: Some("Use it for anything that is not a search operation. Caution! Do not expose it on a public frontend".to_string()), @@ -146,9 +146,9 @@ impl Key { pub fn default_management() -> Self { let now = OffsetDateTime::now_utc(); - let uid = Uuid::new_v4(); + let uid = Uuid::from_u128(1); Self { - name: Some("Default Management API Key".to_string()), + name: Some("Read-only Admin key".to_string()), description: Some("Use it to peek into the instance in a read-only mode. Caution! Do not expose it on a public frontend".to_string()), uid, actions: vec![Action::AllRead], @@ -161,7 +161,7 @@ impl Key { pub fn default_search() -> Self { let now = OffsetDateTime::now_utc(); - let uid = Uuid::new_v4(); + let uid = Uuid::from_u128(2); Self { name: Some("Default Search API Key".to_string()), description: Some("Use it to search from the frontend".to_string()), @@ -176,7 +176,7 @@ impl Key { pub fn default_chat() -> Self { let now = OffsetDateTime::now_utc(); - let uid = Uuid::new_v4(); + let uid = Uuid::from_u128(3); Self { name: Some("Default Chat API Key".to_string()), description: Some("Use it to chat and search from the frontend".to_string()), From f50e586a4f265a06295d3cbd364fa9c8f353002b Mon Sep 17 00:00:00 2001 From: Mubelotix Date: Thu, 19 Jun 2025 11:52:58 +0200 Subject: [PATCH 05/22] Allow management key to read other keys --- crates/meilisearch-types/src/keys.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/meilisearch-types/src/keys.rs b/crates/meilisearch-types/src/keys.rs index 4a3b58c20..bfedf1e99 100644 --- a/crates/meilisearch-types/src/keys.rs +++ b/crates/meilisearch-types/src/keys.rs @@ -148,8 +148,8 @@ impl Key { let now = OffsetDateTime::now_utc(); let uid = Uuid::from_u128(1); Self { - name: Some("Read-only Admin key".to_string()), - description: Some("Use it to peek into the instance in a read-only mode. Caution! Do not expose it on a public frontend".to_string()), + name: Some("Default Read-Only Admin API Key".to_string()), + description: Some("Use it to peek into the instance in a read-only mode. Caution! Do not expose it on a public frontend. It would give access to all other keys".to_string()), uid, actions: vec![Action::AllRead], indexes: vec![IndexUidPattern::all()], @@ -444,7 +444,7 @@ impl Action { SnapshotsCreate => false, Version => true, KeysAdd => false, - KeysGet => false, // Prevent privilege escalation by not allowing reading other keys. + KeysGet => true, KeysUpdate => false, KeysDelete => false, ExperimentalFeaturesGet => true, From b6b7ede266af7221d9271c86ba08d4b9e5d2c0da Mon Sep 17 00:00:00 2001 From: Mubelotix Date: Thu, 19 Jun 2025 11:53:42 +0200 Subject: [PATCH 06/22] Rename Action `*.read` to `*.get` --- crates/meilisearch-types/src/keys.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/meilisearch-types/src/keys.rs b/crates/meilisearch-types/src/keys.rs index bfedf1e99..c3269fb44 100644 --- a/crates/meilisearch-types/src/keys.rs +++ b/crates/meilisearch-types/src/keys.rs @@ -233,8 +233,8 @@ pub enum Action { #[serde(rename = "*")] #[deserr(rename = "*")] All = 0, - #[serde(rename = "*.read")] - #[deserr(rename = "*.read")] + #[serde(rename = "*.get")] + #[deserr(rename = "*.get")] AllRead, #[serde(rename = "search")] #[deserr(rename = "search")] From 9e1cb792f4940ee6e5897192c8925dc0dab2c344 Mon Sep 17 00:00:00 2001 From: Mubelotix Date: Thu, 19 Jun 2025 11:55:25 +0200 Subject: [PATCH 07/22] Rename Action::AllRead to AllGet --- crates/meilisearch-auth/src/store.rs | 2 +- crates/meilisearch-types/src/keys.rs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/meilisearch-auth/src/store.rs b/crates/meilisearch-auth/src/store.rs index 6e4ff8389..9c0ac4b00 100644 --- a/crates/meilisearch-auth/src/store.rs +++ b/crates/meilisearch-auth/src/store.rs @@ -89,7 +89,7 @@ impl HeedAuthStore { for action in &key.actions { match action { Action::All => actions.extend(enum_iterator::all::()), - Action::AllRead => actions.extend(enum_iterator::all::().filter(|a| a.is_read())), + Action::AllGet => actions.extend(enum_iterator::all::().filter(|a| a.is_read())), Action::DocumentsAll => { actions.extend( [Action::DocumentsGet, Action::DocumentsDelete, Action::DocumentsAdd] diff --git a/crates/meilisearch-types/src/keys.rs b/crates/meilisearch-types/src/keys.rs index c3269fb44..b9a9ae21c 100644 --- a/crates/meilisearch-types/src/keys.rs +++ b/crates/meilisearch-types/src/keys.rs @@ -151,7 +151,7 @@ impl Key { name: Some("Default Read-Only Admin API Key".to_string()), description: Some("Use it to peek into the instance in a read-only mode. Caution! Do not expose it on a public frontend. It would give access to all other keys".to_string()), uid, - actions: vec![Action::AllRead], + actions: vec![Action::AllGet], indexes: vec![IndexUidPattern::all()], expires_at: None, created_at: now, @@ -235,7 +235,7 @@ pub enum Action { All = 0, #[serde(rename = "*.get")] #[deserr(rename = "*.get")] - AllRead, + AllGet, #[serde(rename = "search")] #[deserr(rename = "search")] Search, @@ -421,7 +421,7 @@ impl Action { // It's using an exhaustive match to force the addition of new actions. match self { // Any action that expands to others must return false, as it wouldn't be able to expand recursively. - All | AllRead | DocumentsAll | IndexesAll | ChatsAll | TasksAll | SettingsAll + All | AllGet | DocumentsAll | IndexesAll | ChatsAll | TasksAll | SettingsAll | StatsAll | MetricsAll | DumpsAll | SnapshotsAll | ChatsSettingsAll => false, Search => true, @@ -468,7 +468,7 @@ pub mod actions { use super::Action::*; pub(crate) const ALL: u8 = All.repr(); - pub const ALL_READ: u8 = AllRead.repr(); + pub const ALL_READ: u8 = AllGet.repr(); pub const SEARCH: u8 = Search.repr(); pub const DOCUMENTS_ALL: u8 = DocumentsAll.repr(); pub const DOCUMENTS_ADD: u8 = DocumentsAdd.repr(); From 5081d837ea54dcba76038bb96afaa73f7278c189 Mon Sep 17 00:00:00 2001 From: Mubelotix Date: Thu, 19 Jun 2025 12:12:30 +0200 Subject: [PATCH 08/22] Fix AllGet action being included in All --- crates/meilisearch-auth/src/store.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/meilisearch-auth/src/store.rs b/crates/meilisearch-auth/src/store.rs index 9c0ac4b00..bec5d3561 100644 --- a/crates/meilisearch-auth/src/store.rs +++ b/crates/meilisearch-auth/src/store.rs @@ -88,7 +88,10 @@ impl HeedAuthStore { let mut actions = HashSet::new(); for action in &key.actions { match action { - Action::All => actions.extend(enum_iterator::all::()), + Action::All => { + actions.extend(enum_iterator::all::()); + actions.remove(&Action::AllGet); + }, Action::AllGet => actions.extend(enum_iterator::all::().filter(|a| a.is_read())), Action::DocumentsAll => { actions.extend( From 99732f4084475b4955173c3f3c2b96e405b8327a Mon Sep 17 00:00:00 2001 From: Mubelotix Date: Thu, 19 Jun 2025 13:04:55 +0200 Subject: [PATCH 09/22] Fix some tests --- crates/meilisearch-auth/src/lib.rs | 2 +- crates/meilisearch/tests/auth/api_keys.rs | 6 +++--- crates/meilisearch/tests/auth/errors.rs | 6 +++--- crates/meilisearch/tests/common/server.rs | 1 + 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/crates/meilisearch-auth/src/lib.rs b/crates/meilisearch-auth/src/lib.rs index 02d9201c5..000e574ac 100644 --- a/crates/meilisearch-auth/src/lib.rs +++ b/crates/meilisearch-auth/src/lib.rs @@ -351,9 +351,9 @@ pub struct IndexSearchRules { fn generate_default_keys(store: &HeedAuthStore) -> Result<()> { store.put_api_key(Key::default_chat())?; + store.put_api_key(Key::default_management())?; store.put_api_key(Key::default_admin())?; store.put_api_key(Key::default_search())?; - store.put_api_key(Key::default_management())?; Ok(()) } diff --git a/crates/meilisearch/tests/auth/api_keys.rs b/crates/meilisearch/tests/auth/api_keys.rs index 5a18b4dbf..63eb0d21c 100644 --- a/crates/meilisearch/tests/auth/api_keys.rs +++ b/crates/meilisearch/tests/auth/api_keys.rs @@ -419,14 +419,14 @@ async fn error_add_api_key_invalid_parameters_actions() { let (response, code) = server.add_api_key(content).await; meili_snap::snapshot!(code, @"400 Bad Request"); - meili_snap::snapshot!(meili_snap::json_string!(response, { ".createdAt" => "[ignored]", ".updatedAt" => "[ignored]" }), @r###" + meili_snap::snapshot!(meili_snap::json_string!(response, { ".createdAt" => "[ignored]", ".updatedAt" => "[ignored]" }), @r#" { - "message": "Unknown value `doc.add` at `.actions[0]`: expected one of `*`, `search`, `documents.*`, `documents.add`, `documents.get`, `documents.delete`, `indexes.*`, `indexes.create`, `indexes.get`, `indexes.update`, `indexes.delete`, `indexes.swap`, `tasks.*`, `tasks.cancel`, `tasks.delete`, `tasks.get`, `settings.*`, `settings.get`, `settings.update`, `stats.*`, `stats.get`, `metrics.*`, `metrics.get`, `dumps.*`, `dumps.create`, `snapshots.*`, `snapshots.create`, `version`, `keys.create`, `keys.get`, `keys.update`, `keys.delete`, `experimental.get`, `experimental.update`, `network.get`, `network.update`, `chatCompletions`, `chats.*`, `chats.get`, `chats.delete`, `chatsSettings.*`, `chatsSettings.get`, `chatsSettings.update`", + "message": "Unknown value `doc.add` at `.actions[0]`: expected one of `*`, `*.get`, `search`, `documents.*`, `documents.add`, `documents.get`, `documents.delete`, `indexes.*`, `indexes.create`, `indexes.get`, `indexes.update`, `indexes.delete`, `indexes.swap`, `tasks.*`, `tasks.cancel`, `tasks.delete`, `tasks.get`, `settings.*`, `settings.get`, `settings.update`, `stats.*`, `stats.get`, `metrics.*`, `metrics.get`, `dumps.*`, `dumps.create`, `snapshots.*`, `snapshots.create`, `version`, `keys.create`, `keys.get`, `keys.update`, `keys.delete`, `experimental.get`, `experimental.update`, `network.get`, `network.update`, `chatCompletions`, `chats.*`, `chats.get`, `chats.delete`, `chatsSettings.*`, `chatsSettings.get`, `chatsSettings.update`", "code": "invalid_api_key_actions", "type": "invalid_request", "link": "https://docs.meilisearch.com/errors#invalid_api_key_actions" } - "###); + "#); } #[actix_rt::test] diff --git a/crates/meilisearch/tests/auth/errors.rs b/crates/meilisearch/tests/auth/errors.rs index ebe2e53fa..c9fa2ee9c 100644 --- a/crates/meilisearch/tests/auth/errors.rs +++ b/crates/meilisearch/tests/auth/errors.rs @@ -91,14 +91,14 @@ async fn create_api_key_bad_actions() { // can't parse let (response, code) = server.add_api_key(json!({ "actions": ["doggo"] })).await; snapshot!(code, @"400 Bad Request"); - snapshot!(json_string!(response), @r###" + snapshot!(json_string!(response), @r#" { - "message": "Unknown value `doggo` at `.actions[0]`: expected one of `*`, `search`, `documents.*`, `documents.add`, `documents.get`, `documents.delete`, `indexes.*`, `indexes.create`, `indexes.get`, `indexes.update`, `indexes.delete`, `indexes.swap`, `tasks.*`, `tasks.cancel`, `tasks.delete`, `tasks.get`, `settings.*`, `settings.get`, `settings.update`, `stats.*`, `stats.get`, `metrics.*`, `metrics.get`, `dumps.*`, `dumps.create`, `snapshots.*`, `snapshots.create`, `version`, `keys.create`, `keys.get`, `keys.update`, `keys.delete`, `experimental.get`, `experimental.update`, `network.get`, `network.update`, `chatCompletions`, `chats.*`, `chats.get`, `chats.delete`, `chatsSettings.*`, `chatsSettings.get`, `chatsSettings.update`", + "message": "Unknown value `doggo` at `.actions[0]`: expected one of `*`, `*.get`, `search`, `documents.*`, `documents.add`, `documents.get`, `documents.delete`, `indexes.*`, `indexes.create`, `indexes.get`, `indexes.update`, `indexes.delete`, `indexes.swap`, `tasks.*`, `tasks.cancel`, `tasks.delete`, `tasks.get`, `settings.*`, `settings.get`, `settings.update`, `stats.*`, `stats.get`, `metrics.*`, `metrics.get`, `dumps.*`, `dumps.create`, `snapshots.*`, `snapshots.create`, `version`, `keys.create`, `keys.get`, `keys.update`, `keys.delete`, `experimental.get`, `experimental.update`, `network.get`, `network.update`, `chatCompletions`, `chats.*`, `chats.get`, `chats.delete`, `chatsSettings.*`, `chatsSettings.get`, `chatsSettings.update`", "code": "invalid_api_key_actions", "type": "invalid_request", "link": "https://docs.meilisearch.com/errors#invalid_api_key_actions" } - "###); + "#); } #[actix_rt::test] diff --git a/crates/meilisearch/tests/common/server.rs b/crates/meilisearch/tests/common/server.rs index 1f5688a02..19a082cf3 100644 --- a/crates/meilisearch/tests/common/server.rs +++ b/crates/meilisearch/tests/common/server.rs @@ -97,6 +97,7 @@ impl Server { self.use_api_key(master_key); let (response, code) = self.list_api_keys("").await; assert_eq!(200, code, "{:?}", response); + // TODO: relying on the order of keys is not ideal, we should use the static uuid let admin_key = &response["results"][1]["key"]; self.use_api_key(admin_key.as_str().unwrap()); } From 67f2a30d7c9b0b8331912b1ec7471a744e04fb64 Mon Sep 17 00:00:00 2001 From: Mubelotix Date: Thu, 19 Jun 2025 13:10:08 +0200 Subject: [PATCH 10/22] Fix test --- crates/meilisearch/tests/auth/api_keys.rs | 27 ++++++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/crates/meilisearch/tests/auth/api_keys.rs b/crates/meilisearch/tests/auth/api_keys.rs index 63eb0d21c..15edd8f3a 100644 --- a/crates/meilisearch/tests/auth/api_keys.rs +++ b/crates/meilisearch/tests/auth/api_keys.rs @@ -790,7 +790,7 @@ async fn list_api_keys() { meili_snap::snapshot!(code, @"201 Created"); let (response, code) = server.list_api_keys("").await; - meili_snap::snapshot!(meili_snap::json_string!(response, { ".results[].createdAt" => "[ignored]", ".results[].updatedAt" => "[ignored]", ".results[].uid" => "[ignored]", ".results[].key" => "[ignored]" }), @r###" + meili_snap::snapshot!(meili_snap::json_string!(response, { ".results[].createdAt" => "[ignored]", ".results[].updatedAt" => "[ignored]", ".results[0].uid" => "[ignored]", ".results[].key" => "[ignored]" }), @r#" { "results": [ { @@ -824,7 +824,7 @@ async fn list_api_keys() { "name": "Default Search API Key", "description": "Use it to search from the frontend", "key": "[ignored]", - "uid": "[ignored]", + "uid": "00000000-0000-0000-0000-000000000002", "actions": [ "search" ], @@ -839,7 +839,7 @@ async fn list_api_keys() { "name": "Default Admin API Key", "description": "Use it for anything that is not a search operation. Caution! Do not expose it on a public frontend", "key": "[ignored]", - "uid": "[ignored]", + "uid": "00000000-0000-0000-0000-000000000000", "actions": [ "*" ], @@ -850,11 +850,26 @@ async fn list_api_keys() { "createdAt": "[ignored]", "updatedAt": "[ignored]" }, + { + "name": "Default Read-Only Admin API Key", + "description": "Use it to peek into the instance in a read-only mode. Caution! Do not expose it on a public frontend. It would give access to all other keys", + "key": "[ignored]", + "uid": "00000000-0000-0000-0000-000000000001", + "actions": [ + "*.get" + ], + "indexes": [ + "*" + ], + "expiresAt": null, + "createdAt": "[ignored]", + "updatedAt": "[ignored]" + }, { "name": "Default Chat API Key", "description": "Use it to chat and search from the frontend", "key": "[ignored]", - "uid": "[ignored]", + "uid": "00000000-0000-0000-0000-000000000003", "actions": [ "chatCompletions", "search" @@ -869,9 +884,9 @@ async fn list_api_keys() { ], "offset": 0, "limit": 20, - "total": 4 + "total": 5 } - "###); + "#); meili_snap::snapshot!(code, @"200 OK"); } From 705e9a9e5e2464cf5f33758e22272f6ff8984de4 Mon Sep 17 00:00:00 2001 From: Mubelotix Date: Thu, 19 Jun 2025 15:45:09 +0200 Subject: [PATCH 11/22] Make the uuids random again to prevent abuse using rainbow tables --- crates/meilisearch-types/src/keys.rs | 8 ++++---- crates/meilisearch/tests/auth/api_keys.rs | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/crates/meilisearch-types/src/keys.rs b/crates/meilisearch-types/src/keys.rs index b9a9ae21c..3d30c7a0e 100644 --- a/crates/meilisearch-types/src/keys.rs +++ b/crates/meilisearch-types/src/keys.rs @@ -131,7 +131,7 @@ pub struct Key { impl Key { pub fn default_admin() -> Self { let now = OffsetDateTime::now_utc(); - let uid = Uuid::from_u128(0); + let uid = Uuid::new_v4(); Self { name: Some("Default Admin API Key".to_string()), description: Some("Use it for anything that is not a search operation. Caution! Do not expose it on a public frontend".to_string()), @@ -146,7 +146,7 @@ impl Key { pub fn default_management() -> Self { let now = OffsetDateTime::now_utc(); - let uid = Uuid::from_u128(1); + let uid = Uuid::new_v4(); Self { name: Some("Default Read-Only Admin API Key".to_string()), description: Some("Use it to peek into the instance in a read-only mode. Caution! Do not expose it on a public frontend. It would give access to all other keys".to_string()), @@ -161,7 +161,7 @@ impl Key { pub fn default_search() -> Self { let now = OffsetDateTime::now_utc(); - let uid = Uuid::from_u128(2); + let uid = Uuid::new_v4(); Self { name: Some("Default Search API Key".to_string()), description: Some("Use it to search from the frontend".to_string()), @@ -176,7 +176,7 @@ impl Key { pub fn default_chat() -> Self { let now = OffsetDateTime::now_utc(); - let uid = Uuid::from_u128(3); + let uid = Uuid::new_v4(); Self { name: Some("Default Chat API Key".to_string()), description: Some("Use it to chat and search from the frontend".to_string()), diff --git a/crates/meilisearch/tests/auth/api_keys.rs b/crates/meilisearch/tests/auth/api_keys.rs index 15edd8f3a..fa09f17cb 100644 --- a/crates/meilisearch/tests/auth/api_keys.rs +++ b/crates/meilisearch/tests/auth/api_keys.rs @@ -790,7 +790,7 @@ async fn list_api_keys() { meili_snap::snapshot!(code, @"201 Created"); let (response, code) = server.list_api_keys("").await; - meili_snap::snapshot!(meili_snap::json_string!(response, { ".results[].createdAt" => "[ignored]", ".results[].updatedAt" => "[ignored]", ".results[0].uid" => "[ignored]", ".results[].key" => "[ignored]" }), @r#" + meili_snap::snapshot!(meili_snap::json_string!(response, { ".results[].createdAt" => "[ignored]", ".results[].updatedAt" => "[ignored]", ".results[].uid" => "[ignored]", ".results[].key" => "[ignored]" }), @r#" { "results": [ { @@ -824,7 +824,7 @@ async fn list_api_keys() { "name": "Default Search API Key", "description": "Use it to search from the frontend", "key": "[ignored]", - "uid": "00000000-0000-0000-0000-000000000002", + "uid": "[ignored]", "actions": [ "search" ], @@ -839,7 +839,7 @@ async fn list_api_keys() { "name": "Default Admin API Key", "description": "Use it for anything that is not a search operation. Caution! Do not expose it on a public frontend", "key": "[ignored]", - "uid": "00000000-0000-0000-0000-000000000000", + "uid": "[ignored]", "actions": [ "*" ], @@ -854,7 +854,7 @@ async fn list_api_keys() { "name": "Default Read-Only Admin API Key", "description": "Use it to peek into the instance in a read-only mode. Caution! Do not expose it on a public frontend. It would give access to all other keys", "key": "[ignored]", - "uid": "00000000-0000-0000-0000-000000000001", + "uid": "[ignored]", "actions": [ "*.get" ], @@ -869,7 +869,7 @@ async fn list_api_keys() { "name": "Default Chat API Key", "description": "Use it to chat and search from the frontend", "key": "[ignored]", - "uid": "00000000-0000-0000-0000-000000000003", + "uid": "[ignored]", "actions": [ "chatCompletions", "search" From ab768f379ff54ee66f9f3bdf4f643e92a04e8e92 Mon Sep 17 00:00:00 2001 From: Mubelotix Date: Thu, 19 Jun 2025 15:49:34 +0200 Subject: [PATCH 12/22] Fix comment --- crates/meilisearch/tests/common/server.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/meilisearch/tests/common/server.rs b/crates/meilisearch/tests/common/server.rs index 19a082cf3..671ed1ab6 100644 --- a/crates/meilisearch/tests/common/server.rs +++ b/crates/meilisearch/tests/common/server.rs @@ -97,7 +97,7 @@ impl Server { self.use_api_key(master_key); let (response, code) = self.list_api_keys("").await; assert_eq!(200, code, "{:?}", response); - // TODO: relying on the order of keys is not ideal, we should use the static uuid + // TODO: relying on the order of keys is not ideal, we should use the name instead let admin_key = &response["results"][1]["key"]; self.use_api_key(admin_key.as_str().unwrap()); } From 2d6dc83940fe394389e91a6e53e63f53e36b3a22 Mon Sep 17 00:00:00 2001 From: Mubelotix Date: Thu, 19 Jun 2025 15:55:12 +0200 Subject: [PATCH 13/22] Format the code --- crates/meilisearch-auth/src/store.rs | 6 ++++-- crates/meilisearch-types/src/keys.rs | 2 +- crates/meilisearch/tests/index/stats.rs | 1 - 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/crates/meilisearch-auth/src/store.rs b/crates/meilisearch-auth/src/store.rs index bec5d3561..eb2170f08 100644 --- a/crates/meilisearch-auth/src/store.rs +++ b/crates/meilisearch-auth/src/store.rs @@ -91,8 +91,10 @@ impl HeedAuthStore { Action::All => { actions.extend(enum_iterator::all::()); actions.remove(&Action::AllGet); - }, - Action::AllGet => actions.extend(enum_iterator::all::().filter(|a| a.is_read())), + } + Action::AllGet => { + actions.extend(enum_iterator::all::().filter(|a| a.is_read())) + } Action::DocumentsAll => { actions.extend( [Action::DocumentsGet, Action::DocumentsDelete, Action::DocumentsAdd] diff --git a/crates/meilisearch-types/src/keys.rs b/crates/meilisearch-types/src/keys.rs index 3d30c7a0e..48f908a81 100644 --- a/crates/meilisearch-types/src/keys.rs +++ b/crates/meilisearch-types/src/keys.rs @@ -422,7 +422,7 @@ impl Action { match self { // Any action that expands to others must return false, as it wouldn't be able to expand recursively. All | AllGet | DocumentsAll | IndexesAll | ChatsAll | TasksAll | SettingsAll - | StatsAll | MetricsAll | DumpsAll | SnapshotsAll | ChatsSettingsAll => false, + | StatsAll | MetricsAll | DumpsAll | SnapshotsAll | ChatsSettingsAll => false, Search => true, DocumentsAdd => false, diff --git a/crates/meilisearch/tests/index/stats.rs b/crates/meilisearch/tests/index/stats.rs index 90c77cec8..6b2ba16ac 100644 --- a/crates/meilisearch/tests/index/stats.rs +++ b/crates/meilisearch/tests/index/stats.rs @@ -1,5 +1,4 @@ use crate::common::{shared_does_not_exists_index, Server}; - use crate::json; #[actix_rt::test] From c4a96b40eb050d85406d04ec4c7402e98140cde6 Mon Sep 17 00:00:00 2001 From: Mubelotix Date: Tue, 24 Jun 2025 17:40:06 +0200 Subject: [PATCH 14/22] Remove KeysGet from AllGet --- crates/meilisearch-types/src/keys.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/meilisearch-types/src/keys.rs b/crates/meilisearch-types/src/keys.rs index 48f908a81..e4a0dd5d8 100644 --- a/crates/meilisearch-types/src/keys.rs +++ b/crates/meilisearch-types/src/keys.rs @@ -151,7 +151,7 @@ impl Key { name: Some("Default Read-Only Admin API Key".to_string()), description: Some("Use it to peek into the instance in a read-only mode. Caution! Do not expose it on a public frontend. It would give access to all other keys".to_string()), uid, - actions: vec![Action::AllGet], + actions: vec![Action::AllGet, Action::KeysGet], indexes: vec![IndexUidPattern::all()], expires_at: None, created_at: now, @@ -444,14 +444,14 @@ impl Action { SnapshotsCreate => false, Version => true, KeysAdd => false, - KeysGet => true, + KeysGet => false, // Disabled in order to prevent privilege escalation KeysUpdate => false, KeysDelete => false, ExperimentalFeaturesGet => true, ExperimentalFeaturesUpdate => false, NetworkGet => true, NetworkUpdate => false, - ChatCompletions => false, // Disabled because it might trigger generation of new chats. + ChatCompletions => false, // Disabled because it might trigger generation of new chats ChatsGet => true, ChatsDelete => false, ChatsSettingsGet => true, From 1c8f1c18f4fd1969613e75b80ca39c171f65cf3d Mon Sep 17 00:00:00 2001 From: Mubelotix Date: Wed, 25 Jun 2025 09:59:34 +0200 Subject: [PATCH 15/22] Fix constant name and key description --- crates/meilisearch-types/src/keys.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/meilisearch-types/src/keys.rs b/crates/meilisearch-types/src/keys.rs index e4a0dd5d8..96b2e8ae1 100644 --- a/crates/meilisearch-types/src/keys.rs +++ b/crates/meilisearch-types/src/keys.rs @@ -149,7 +149,7 @@ impl Key { let uid = Uuid::new_v4(); Self { name: Some("Default Read-Only Admin API Key".to_string()), - description: Some("Use it to peek into the instance in a read-only mode. Caution! Do not expose it on a public frontend. It would give access to all other keys".to_string()), + description: Some("Use it to peek into the instance in a read-only mode.".to_string()), uid, actions: vec![Action::AllGet, Action::KeysGet], indexes: vec![IndexUidPattern::all()], @@ -468,7 +468,7 @@ pub mod actions { use super::Action::*; pub(crate) const ALL: u8 = All.repr(); - pub const ALL_READ: u8 = AllGet.repr(); + pub const ALL_GET: u8 = AllGet.repr(); pub const SEARCH: u8 = Search.repr(); pub const DOCUMENTS_ALL: u8 = DocumentsAll.repr(); pub const DOCUMENTS_ADD: u8 = DocumentsAdd.repr(); From 2090e9ea316b2dedfa272ba4a00d3b20109d8867 Mon Sep 17 00:00:00 2001 From: Mubelotix Date: Wed, 25 Jun 2025 10:08:25 +0200 Subject: [PATCH 16/22] Update test --- crates/meilisearch/tests/auth/api_keys.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/meilisearch/tests/auth/api_keys.rs b/crates/meilisearch/tests/auth/api_keys.rs index fa09f17cb..60cb2ff46 100644 --- a/crates/meilisearch/tests/auth/api_keys.rs +++ b/crates/meilisearch/tests/auth/api_keys.rs @@ -852,11 +852,12 @@ async fn list_api_keys() { }, { "name": "Default Read-Only Admin API Key", - "description": "Use it to peek into the instance in a read-only mode. Caution! Do not expose it on a public frontend. It would give access to all other keys", + "description": "Use it to peek into the instance in a read-only mode.", "key": "[ignored]", "uid": "[ignored]", "actions": [ - "*.get" + "*.get", + "keys.get" ], "indexes": [ "*" From c15763f9104d809cad0e9f1889de016addd27da8 Mon Sep 17 00:00:00 2001 From: Mubelotix Date: Fri, 27 Jun 2025 09:39:24 +0200 Subject: [PATCH 17/22] Improve key description Co-authored-by: Tamo --- crates/meilisearch-auth/src/lib.rs | 2 +- crates/meilisearch-types/src/keys.rs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/meilisearch-auth/src/lib.rs b/crates/meilisearch-auth/src/lib.rs index 000e574ac..582606651 100644 --- a/crates/meilisearch-auth/src/lib.rs +++ b/crates/meilisearch-auth/src/lib.rs @@ -351,7 +351,7 @@ pub struct IndexSearchRules { fn generate_default_keys(store: &HeedAuthStore) -> Result<()> { store.put_api_key(Key::default_chat())?; - store.put_api_key(Key::default_management())?; + store.put_api_key(Key::default_read_only_admin_key())?; store.put_api_key(Key::default_admin())?; store.put_api_key(Key::default_search())?; diff --git a/crates/meilisearch-types/src/keys.rs b/crates/meilisearch-types/src/keys.rs index 96b2e8ae1..4a4bc40a8 100644 --- a/crates/meilisearch-types/src/keys.rs +++ b/crates/meilisearch-types/src/keys.rs @@ -144,14 +144,14 @@ impl Key { } } - pub fn default_management() -> Self { + pub fn default_read_only_admin_key() -> Self { let now = OffsetDateTime::now_utc(); let uid = Uuid::new_v4(); Self { name: Some("Default Read-Only Admin API Key".to_string()), - description: Some("Use it to peek into the instance in a read-only mode.".to_string()), + description: Some("Use it to peek into the instance in a read-only mode. Caution: This key gives you access to all the other api keys. Do not expose it on a public frontend".to_string()), uid, - actions: vec![Action::AllGet, Action::KeysGet], + actions: vec![Action::AllGet, Action::KeysGedt], indexes: vec![IndexUidPattern::all()], expires_at: None, created_at: now, From fb9170b8e3120f535a0a1949b8227ac5f862dd94 Mon Sep 17 00:00:00 2001 From: Mubelotix Date: Fri, 27 Jun 2025 09:40:30 +0200 Subject: [PATCH 18/22] Keep name consistent with others --- crates/meilisearch-auth/src/lib.rs | 2 +- crates/meilisearch-types/src/keys.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/meilisearch-auth/src/lib.rs b/crates/meilisearch-auth/src/lib.rs index 582606651..6f5a5c2a2 100644 --- a/crates/meilisearch-auth/src/lib.rs +++ b/crates/meilisearch-auth/src/lib.rs @@ -351,7 +351,7 @@ pub struct IndexSearchRules { fn generate_default_keys(store: &HeedAuthStore) -> Result<()> { store.put_api_key(Key::default_chat())?; - store.put_api_key(Key::default_read_only_admin_key())?; + store.put_api_key(Key::default_read_only_admin())?; store.put_api_key(Key::default_admin())?; store.put_api_key(Key::default_search())?; diff --git a/crates/meilisearch-types/src/keys.rs b/crates/meilisearch-types/src/keys.rs index 4a4bc40a8..7f10e9265 100644 --- a/crates/meilisearch-types/src/keys.rs +++ b/crates/meilisearch-types/src/keys.rs @@ -144,7 +144,7 @@ impl Key { } } - pub fn default_read_only_admin_key() -> Self { + pub fn default_read_only_admin() -> Self { let now = OffsetDateTime::now_utc(); let uid = Uuid::new_v4(); Self { From e3fba62e13ebfc0615fdaac06dc27fd2a9085407 Mon Sep 17 00:00:00 2001 From: Mubelotix Date: Fri, 27 Jun 2025 09:40:59 +0200 Subject: [PATCH 19/22] Fix typo --- crates/meilisearch-types/src/keys.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/meilisearch-types/src/keys.rs b/crates/meilisearch-types/src/keys.rs index 7f10e9265..2911f22a2 100644 --- a/crates/meilisearch-types/src/keys.rs +++ b/crates/meilisearch-types/src/keys.rs @@ -151,7 +151,7 @@ impl Key { name: Some("Default Read-Only Admin API Key".to_string()), description: Some("Use it to peek into the instance in a read-only mode. Caution: This key gives you access to all the other api keys. Do not expose it on a public frontend".to_string()), uid, - actions: vec![Action::AllGet, Action::KeysGedt], + actions: vec![Action::AllGet, Action::KeysGet], indexes: vec![IndexUidPattern::all()], expires_at: None, created_at: now, From 28adbc0d1878c365e295aabdf41a7089a5c4c01e Mon Sep 17 00:00:00 2001 From: Mubelotix Date: Fri, 27 Jun 2025 09:47:46 +0200 Subject: [PATCH 20/22] Update tests --- crates/meilisearch/tests/auth/api_keys.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/meilisearch/tests/auth/api_keys.rs b/crates/meilisearch/tests/auth/api_keys.rs index 60cb2ff46..f717fd53e 100644 --- a/crates/meilisearch/tests/auth/api_keys.rs +++ b/crates/meilisearch/tests/auth/api_keys.rs @@ -852,7 +852,7 @@ async fn list_api_keys() { }, { "name": "Default Read-Only Admin API Key", - "description": "Use it to peek into the instance in a read-only mode.", + "description": "Use it to peek into the instance in a read-only mode. Caution: This key gives you access to all the other api keys. Do not expose it on a public frontend", "key": "[ignored]", "uid": "[ignored]", "actions": [ From a56c0369947760bf5980d4dfac97bec1951c0781 Mon Sep 17 00:00:00 2001 From: Mubelotix Date: Tue, 8 Jul 2025 12:18:52 +0200 Subject: [PATCH 21/22] Update crates/meilisearch-types/src/keys.rs Co-authored-by: gui machiavelli --- crates/meilisearch-types/src/keys.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/meilisearch-types/src/keys.rs b/crates/meilisearch-types/src/keys.rs index 2911f22a2..b98f2d38d 100644 --- a/crates/meilisearch-types/src/keys.rs +++ b/crates/meilisearch-types/src/keys.rs @@ -149,7 +149,7 @@ impl Key { let uid = Uuid::new_v4(); Self { name: Some("Default Read-Only Admin API Key".to_string()), - description: Some("Use it to peek into the instance in a read-only mode. Caution: This key gives you access to all the other api keys. Do not expose it on a public frontend".to_string()), + description: Some("Use it to read information across the whole database. Caution! Do not expose this key on a public frontend".to_string()), uid, actions: vec![Action::AllGet, Action::KeysGet], indexes: vec![IndexUidPattern::all()], From 9cee432255f1a66b6b16c6b46bea243902eade2d Mon Sep 17 00:00:00 2001 From: Mubelotix Date: Tue, 8 Jul 2025 13:36:26 +0200 Subject: [PATCH 22/22] Fix broken tests --- crates/meilisearch-types/src/keys.rs | 1 + crates/meilisearch/tests/auth/api_keys.rs | 4 ++-- crates/meilisearch/tests/auth/errors.rs | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/crates/meilisearch-types/src/keys.rs b/crates/meilisearch-types/src/keys.rs index c0ec5ae0b..e210f8df3 100644 --- a/crates/meilisearch-types/src/keys.rs +++ b/crates/meilisearch-types/src/keys.rs @@ -431,6 +431,7 @@ impl Action { DocumentsAdd => false, DocumentsGet => true, DocumentsDelete => false, + Export => true, IndexesAdd => false, IndexesGet => true, IndexesUpdate => false, diff --git a/crates/meilisearch/tests/auth/api_keys.rs b/crates/meilisearch/tests/auth/api_keys.rs index f717fd53e..0b8a3d2c5 100644 --- a/crates/meilisearch/tests/auth/api_keys.rs +++ b/crates/meilisearch/tests/auth/api_keys.rs @@ -421,7 +421,7 @@ async fn error_add_api_key_invalid_parameters_actions() { meili_snap::snapshot!(code, @"400 Bad Request"); meili_snap::snapshot!(meili_snap::json_string!(response, { ".createdAt" => "[ignored]", ".updatedAt" => "[ignored]" }), @r#" { - "message": "Unknown value `doc.add` at `.actions[0]`: expected one of `*`, `*.get`, `search`, `documents.*`, `documents.add`, `documents.get`, `documents.delete`, `indexes.*`, `indexes.create`, `indexes.get`, `indexes.update`, `indexes.delete`, `indexes.swap`, `tasks.*`, `tasks.cancel`, `tasks.delete`, `tasks.get`, `settings.*`, `settings.get`, `settings.update`, `stats.*`, `stats.get`, `metrics.*`, `metrics.get`, `dumps.*`, `dumps.create`, `snapshots.*`, `snapshots.create`, `version`, `keys.create`, `keys.get`, `keys.update`, `keys.delete`, `experimental.get`, `experimental.update`, `network.get`, `network.update`, `chatCompletions`, `chats.*`, `chats.get`, `chats.delete`, `chatsSettings.*`, `chatsSettings.get`, `chatsSettings.update`", + "message": "Unknown value `doc.add` at `.actions[0]`: expected one of `*`, `*.get`, `search`, `documents.*`, `documents.add`, `documents.get`, `documents.delete`, `indexes.*`, `indexes.create`, `indexes.get`, `indexes.update`, `indexes.delete`, `indexes.swap`, `tasks.*`, `tasks.cancel`, `tasks.delete`, `tasks.get`, `settings.*`, `settings.get`, `settings.update`, `stats.*`, `stats.get`, `metrics.*`, `metrics.get`, `dumps.*`, `dumps.create`, `snapshots.*`, `snapshots.create`, `version`, `keys.create`, `keys.get`, `keys.update`, `keys.delete`, `experimental.get`, `experimental.update`, `export`, `network.get`, `network.update`, `chatCompletions`, `chats.*`, `chats.get`, `chats.delete`, `chatsSettings.*`, `chatsSettings.get`, `chatsSettings.update`", "code": "invalid_api_key_actions", "type": "invalid_request", "link": "https://docs.meilisearch.com/errors#invalid_api_key_actions" @@ -852,7 +852,7 @@ async fn list_api_keys() { }, { "name": "Default Read-Only Admin API Key", - "description": "Use it to peek into the instance in a read-only mode. Caution: This key gives you access to all the other api keys. Do not expose it on a public frontend", + "description": "Use it to read information across the whole database. Caution! Do not expose this key on a public frontend", "key": "[ignored]", "uid": "[ignored]", "actions": [ diff --git a/crates/meilisearch/tests/auth/errors.rs b/crates/meilisearch/tests/auth/errors.rs index 845fe7085..e8d935fde 100644 --- a/crates/meilisearch/tests/auth/errors.rs +++ b/crates/meilisearch/tests/auth/errors.rs @@ -93,7 +93,7 @@ async fn create_api_key_bad_actions() { snapshot!(code, @"400 Bad Request"); snapshot!(json_string!(response), @r#" { - "message": "Unknown value `doggo` at `.actions[0]`: expected one of `*`, `search`, `documents.*`, `documents.add`, `documents.get`, `documents.delete`, `indexes.*`, `indexes.create`, `indexes.get`, `indexes.update`, `indexes.delete`, `indexes.swap`, `tasks.*`, `tasks.cancel`, `tasks.delete`, `tasks.get`, `settings.*`, `settings.get`, `settings.update`, `stats.*`, `stats.get`, `metrics.*`, `metrics.get`, `dumps.*`, `dumps.create`, `snapshots.*`, `snapshots.create`, `version`, `keys.create`, `keys.get`, `keys.update`, `keys.delete`, `experimental.get`, `experimental.update`, `export`, `network.get`, `network.update`, `chatCompletions`, `chats.*`, `chats.get`, `chats.delete`, `chatsSettings.*`, `chatsSettings.get`, `chatsSettings.update`", + "message": "Unknown value `doggo` at `.actions[0]`: expected one of `*`, `*.get`, `search`, `documents.*`, `documents.add`, `documents.get`, `documents.delete`, `indexes.*`, `indexes.create`, `indexes.get`, `indexes.update`, `indexes.delete`, `indexes.swap`, `tasks.*`, `tasks.cancel`, `tasks.delete`, `tasks.get`, `settings.*`, `settings.get`, `settings.update`, `stats.*`, `stats.get`, `metrics.*`, `metrics.get`, `dumps.*`, `dumps.create`, `snapshots.*`, `snapshots.create`, `version`, `keys.create`, `keys.get`, `keys.update`, `keys.delete`, `experimental.get`, `experimental.update`, `export`, `network.get`, `network.update`, `chatCompletions`, `chats.*`, `chats.get`, `chats.delete`, `chatsSettings.*`, `chatsSettings.get`, `chatsSettings.update`", "code": "invalid_api_key_actions", "type": "invalid_request", "link": "https://docs.meilisearch.com/errors#invalid_api_key_actions"