mirror of
https://github.com/meilisearch/MeiliSearch
synced 2024-11-25 22:34:28 +01:00
move the API key in meilisearch_types
This commit is contained in:
parent
c192146fbe
commit
7034803712
28
Cargo.lock
generated
28
Cargo.lock
generated
@ -1221,13 +1221,33 @@ dependencies = [
|
|||||||
"cfg-if",
|
"cfg-if",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "enum-iterator"
|
||||||
|
version = "0.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4eeac5c5edb79e4e39fe8439ef35207780a11f69c52cbe424ce3dfad4cb78de6"
|
||||||
|
dependencies = [
|
||||||
|
"enum-iterator-derive 0.7.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "enum-iterator"
|
name = "enum-iterator"
|
||||||
version = "1.1.3"
|
version = "1.1.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "45a0ac4aeb3a18f92eaf09c6bb9b3ac30ff61ca95514fc58cbead1c9a6bf5401"
|
checksum = "45a0ac4aeb3a18f92eaf09c6bb9b3ac30ff61ca95514fc58cbead1c9a6bf5401"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"enum-iterator-derive",
|
"enum-iterator-derive 1.1.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "enum-iterator-derive"
|
||||||
|
version = "0.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c134c37760b27a871ba422106eedbb8247da973a09e82558bf26d619c882b159"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2 1.0.46",
|
||||||
|
"quote 1.0.21",
|
||||||
|
"syn 1.0.101",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -2262,7 +2282,7 @@ dependencies = [
|
|||||||
name = "meilisearch-auth"
|
name = "meilisearch-auth"
|
||||||
version = "0.29.1"
|
version = "0.29.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"enum-iterator",
|
"enum-iterator 1.1.3",
|
||||||
"hmac",
|
"hmac",
|
||||||
"meilisearch-types",
|
"meilisearch-types",
|
||||||
"milli 0.34.0",
|
"milli 0.34.0",
|
||||||
@ -2363,6 +2383,7 @@ dependencies = [
|
|||||||
"actix-web",
|
"actix-web",
|
||||||
"csv",
|
"csv",
|
||||||
"either",
|
"either",
|
||||||
|
"enum-iterator 0.7.0",
|
||||||
"insta",
|
"insta",
|
||||||
"meili-snap",
|
"meili-snap",
|
||||||
"milli 0.33.4",
|
"milli 0.33.4",
|
||||||
@ -2370,6 +2391,7 @@ dependencies = [
|
|||||||
"proptest-derive",
|
"proptest-derive",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"thiserror",
|
||||||
"time",
|
"time",
|
||||||
"tokio",
|
"tokio",
|
||||||
"uuid 1.1.2",
|
"uuid 1.1.2",
|
||||||
@ -4034,7 +4056,7 @@ checksum = "73ba753d713ec3844652ad2cb7eb56bc71e34213a14faddac7852a10ba88f61e"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"enum-iterator",
|
"enum-iterator 1.1.3",
|
||||||
"getset",
|
"getset",
|
||||||
"git2",
|
"git2",
|
||||||
"rustversion",
|
"rustversion",
|
||||||
|
@ -5,8 +5,8 @@ use std::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use flate2::{write::GzEncoder, Compression};
|
use flate2::{write::GzEncoder, Compression};
|
||||||
use meilisearch_auth::Key;
|
|
||||||
use meilisearch_types::{
|
use meilisearch_types::{
|
||||||
|
keys::Key,
|
||||||
settings::{Checked, Settings},
|
settings::{Checked, Settings},
|
||||||
tasks::Task,
|
tasks::Task,
|
||||||
};
|
};
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use meilisearch_types::milli::update::IndexDocumentsMethod::{
|
use meilisearch_types::milli::update::IndexDocumentsMethod::{
|
||||||
self, ReplaceDocuments, UpdateDocuments,
|
self, ReplaceDocuments, UpdateDocuments,
|
||||||
};
|
};
|
||||||
use meilisearch_types::tasks::{Kind, TaskId};
|
use meilisearch_types::tasks::TaskId;
|
||||||
use std::ops::ControlFlow::{self, Break, Continue};
|
use std::ops::ControlFlow::{self, Break, Continue};
|
||||||
|
|
||||||
use crate::KindWithContent;
|
use crate::KindWithContent;
|
||||||
@ -50,7 +50,7 @@ impl From<KindWithContent> for AutobatchKind {
|
|||||||
KindWithContent::IndexDeletion { .. } => AutobatchKind::IndexDeletion,
|
KindWithContent::IndexDeletion { .. } => AutobatchKind::IndexDeletion,
|
||||||
KindWithContent::IndexCreation { .. } => AutobatchKind::IndexCreation,
|
KindWithContent::IndexCreation { .. } => AutobatchKind::IndexCreation,
|
||||||
KindWithContent::IndexUpdate { .. } => AutobatchKind::IndexUpdate,
|
KindWithContent::IndexUpdate { .. } => AutobatchKind::IndexUpdate,
|
||||||
KindWithContent::IndexSwap { lhs, rhs } => AutobatchKind::IndexSwap,
|
KindWithContent::IndexSwap { .. } => AutobatchKind::IndexSwap,
|
||||||
KindWithContent::CancelTask { .. } => AutobatchKind::CancelTask,
|
KindWithContent::CancelTask { .. } => AutobatchKind::CancelTask,
|
||||||
KindWithContent::DeleteTasks { .. } => AutobatchKind::DeleteTasks,
|
KindWithContent::DeleteTasks { .. } => AutobatchKind::DeleteTasks,
|
||||||
KindWithContent::DumpExport { .. } => AutobatchKind::DumpExport,
|
KindWithContent::DumpExport { .. } => AutobatchKind::DumpExport,
|
||||||
|
@ -1,37 +1,18 @@
|
|||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
|
|
||||||
use meilisearch_types::error::{Code, ErrorCode};
|
use meilisearch_types::error::{Code, ErrorCode};
|
||||||
use meilisearch_types::internal_error;
|
use meilisearch_types::{internal_error, keys};
|
||||||
use serde_json::Value;
|
|
||||||
|
|
||||||
pub type Result<T> = std::result::Result<T, AuthControllerError>;
|
pub type Result<T> = std::result::Result<T, AuthControllerError>;
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum AuthControllerError {
|
pub enum AuthControllerError {
|
||||||
#[error("`{0}` field is mandatory.")]
|
|
||||||
MissingParameter(&'static str),
|
|
||||||
#[error("`actions` field value `{0}` is invalid. It should be an array of string representing action names.")]
|
|
||||||
InvalidApiKeyActions(Value),
|
|
||||||
#[error(
|
|
||||||
"`{0}` is not a valid index uid. It should be an array of string representing index names."
|
|
||||||
)]
|
|
||||||
InvalidApiKeyIndexes(Value),
|
|
||||||
#[error("`expiresAt` field value `{0}` is invalid. It should follow the RFC 3339 format to represents a date or datetime in the future or specified as a null value. e.g. 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS'.")]
|
|
||||||
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 UUID v4 string or omitted.")]
|
|
||||||
InvalidApiKeyUid(Value),
|
|
||||||
#[error("API key `{0}` not found.")]
|
#[error("API key `{0}` not found.")]
|
||||||
ApiKeyNotFound(String),
|
ApiKeyNotFound(String),
|
||||||
#[error("`uid` field value `{0}` is already an existing API key.")]
|
#[error("`uid` field value `{0}` is already an existing API key.")]
|
||||||
ApiKeyAlreadyExists(String),
|
ApiKeyAlreadyExists(String),
|
||||||
#[error("The `{0}` field cannot be modified for the given resource.")]
|
#[error(transparent)]
|
||||||
ImmutableField(String),
|
ApiKey(#[from] keys::Error),
|
||||||
#[error("Internal error: {0}")]
|
#[error("Internal error: {0}")]
|
||||||
Internal(Box<dyn Error + Send + Sync + 'static>),
|
Internal(Box<dyn Error + Send + Sync + 'static>),
|
||||||
}
|
}
|
||||||
@ -46,16 +27,9 @@ internal_error!(
|
|||||||
impl ErrorCode for AuthControllerError {
|
impl ErrorCode for AuthControllerError {
|
||||||
fn error_code(&self) -> Code {
|
fn error_code(&self) -> Code {
|
||||||
match self {
|
match self {
|
||||||
Self::MissingParameter(_) => Code::MissingParameter,
|
Self::ApiKey(e) => e.error_code(),
|
||||||
Self::InvalidApiKeyActions(_) => Code::InvalidApiKeyActions,
|
|
||||||
Self::InvalidApiKeyIndexes(_) => Code::InvalidApiKeyIndexes,
|
|
||||||
Self::InvalidApiKeyExpiresAt(_) => Code::InvalidApiKeyExpiresAt,
|
|
||||||
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::ApiKeyAlreadyExists(_) => Code::ApiKeyAlreadyExists,
|
||||||
Self::ImmutableField(_) => Code::ImmutableField,
|
|
||||||
Self::Internal(_) => Code::Internal,
|
Self::Internal(_) => Code::Internal,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,201 +0,0 @@
|
|||||||
use crate::action::Action;
|
|
||||||
use crate::error::{AuthControllerError, Result};
|
|
||||||
use crate::store::KeyId;
|
|
||||||
|
|
||||||
use meilisearch_types::index_uid::IndexUid;
|
|
||||||
use meilisearch_types::star_or::StarOr;
|
|
||||||
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, PartialEq, Eq, Deserialize, Serialize)]
|
|
||||||
pub struct Key {
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub description: Option<String>,
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub name: Option<String>,
|
|
||||||
pub uid: KeyId,
|
|
||||||
pub actions: Vec<Action>,
|
|
||||||
pub indexes: Vec<StarOr<IndexUid>>,
|
|
||||||
#[serde(with = "time::serde::rfc3339::option")]
|
|
||||||
pub expires_at: Option<OffsetDateTime>,
|
|
||||||
#[serde(with = "time::serde::rfc3339")]
|
|
||||||
pub created_at: OffsetDateTime,
|
|
||||||
#[serde(with = "time::serde::rfc3339")]
|
|
||||||
pub updated_at: OffsetDateTime,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Key {
|
|
||||||
pub fn create_from_value(value: Value) -> Result<Self> {
|
|
||||||
let name = match value.get("name") {
|
|
||||||
None | Some(Value::Null) => None,
|
|
||||||
Some(des) => from_value(des.clone())
|
|
||||||
.map(Some)
|
|
||||||
.map_err(|_| AuthControllerError::InvalidApiKeyName(des.clone()))?,
|
|
||||||
};
|
|
||||||
|
|
||||||
let description = match value.get("description") {
|
|
||||||
None | Some(Value::Null) => None,
|
|
||||||
Some(des) => from_value(des.clone())
|
|
||||||
.map(Some)
|
|
||||||
.map_err(|_| AuthControllerError::InvalidApiKeyDescription(des.clone()))?,
|
|
||||||
};
|
|
||||||
|
|
||||||
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")
|
|
||||||
.map(|act| {
|
|
||||||
from_value(act.clone())
|
|
||||||
.map_err(|_| AuthControllerError::InvalidApiKeyActions(act.clone()))
|
|
||||||
})
|
|
||||||
.ok_or(AuthControllerError::MissingParameter("actions"))??;
|
|
||||||
|
|
||||||
let indexes = value
|
|
||||||
.get("indexes")
|
|
||||||
.map(|ind| {
|
|
||||||
from_value(ind.clone())
|
|
||||||
.map_err(|_| AuthControllerError::InvalidApiKeyIndexes(ind.clone()))
|
|
||||||
})
|
|
||||||
.ok_or(AuthControllerError::MissingParameter("indexes"))??;
|
|
||||||
|
|
||||||
let expires_at = value
|
|
||||||
.get("expiresAt")
|
|
||||||
.map(parse_expiration_date)
|
|
||||||
.ok_or(AuthControllerError::MissingParameter("expiresAt"))??;
|
|
||||||
|
|
||||||
let created_at = OffsetDateTime::now_utc();
|
|
||||||
let updated_at = created_at;
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
name,
|
|
||||||
description,
|
|
||||||
uid,
|
|
||||||
actions,
|
|
||||||
indexes,
|
|
||||||
expires_at,
|
|
||||||
created_at,
|
|
||||||
updated_at,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update_from_value(&mut self, value: Value) -> Result<()> {
|
|
||||||
if let Some(des) = value.get("description") {
|
|
||||||
let des = from_value(des.clone())
|
|
||||||
.map_err(|_| AuthControllerError::InvalidApiKeyDescription(des.clone()));
|
|
||||||
self.description = des?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(des) = value.get("name") {
|
|
||||||
let des = from_value(des.clone())
|
|
||||||
.map_err(|_| AuthControllerError::InvalidApiKeyName(des.clone()));
|
|
||||||
self.name = des?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if value.get("uid").is_some() {
|
|
||||||
return Err(AuthControllerError::ImmutableField("uid".to_string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
if value.get("actions").is_some() {
|
|
||||||
return Err(AuthControllerError::ImmutableField("actions".to_string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
if value.get("indexes").is_some() {
|
|
||||||
return Err(AuthControllerError::ImmutableField("indexes".to_string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
if value.get("expiresAt").is_some() {
|
|
||||||
return Err(AuthControllerError::ImmutableField("expiresAt".to_string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
if value.get("createdAt").is_some() {
|
|
||||||
return Err(AuthControllerError::ImmutableField("createdAt".to_string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
if value.get("updatedAt").is_some() {
|
|
||||||
return Err(AuthControllerError::ImmutableField("updatedAt".to_string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
self.updated_at = OffsetDateTime::now_utc();
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn default_admin() -> Self {
|
|
||||||
let now = OffsetDateTime::now_utc();
|
|
||||||
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()),
|
|
||||||
uid,
|
|
||||||
actions: vec![Action::All],
|
|
||||||
indexes: vec![StarOr::Star],
|
|
||||||
expires_at: None,
|
|
||||||
created_at: now,
|
|
||||||
updated_at: now,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn default_search() -> Self {
|
|
||||||
let now = OffsetDateTime::now_utc();
|
|
||||||
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()),
|
|
||||||
uid,
|
|
||||||
actions: vec![Action::Search],
|
|
||||||
indexes: vec![StarOr::Star],
|
|
||||||
expires_at: None,
|
|
||||||
created_at: now,
|
|
||||||
updated_at: now,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_expiration_date(value: &Value) -> Result<Option<OffsetDateTime>> {
|
|
||||||
match value {
|
|
||||||
Value::String(string) => OffsetDateTime::parse(string, &Rfc3339)
|
|
||||||
.or_else(|_| {
|
|
||||||
PrimitiveDateTime::parse(
|
|
||||||
string,
|
|
||||||
format_description!(
|
|
||||||
"[year repr:full base:calendar]-[month repr:numerical]-[day]T[hour]:[minute]:[second]"
|
|
||||||
),
|
|
||||||
).map(|datetime| datetime.assume_utc())
|
|
||||||
})
|
|
||||||
.or_else(|_| {
|
|
||||||
PrimitiveDateTime::parse(
|
|
||||||
string,
|
|
||||||
format_description!(
|
|
||||||
"[year repr:full base:calendar]-[month repr:numerical]-[day] [hour]:[minute]:[second]"
|
|
||||||
),
|
|
||||||
).map(|datetime| datetime.assume_utc())
|
|
||||||
})
|
|
||||||
.or_else(|_| {
|
|
||||||
Date::parse(string, format_description!(
|
|
||||||
"[year repr:full base:calendar]-[month repr:numerical]-[day]"
|
|
||||||
)).map(|date| PrimitiveDateTime::new(date, time!(00:00)).assume_utc())
|
|
||||||
})
|
|
||||||
.map_err(|_| AuthControllerError::InvalidApiKeyExpiresAt(value.clone()))
|
|
||||||
// check if the key is already expired.
|
|
||||||
.and_then(|d| {
|
|
||||||
if d > OffsetDateTime::now_utc() {
|
|
||||||
Ok(d)
|
|
||||||
} else {
|
|
||||||
Err(AuthControllerError::InvalidApiKeyExpiresAt(value.clone()))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.map(Option::Some),
|
|
||||||
Value::Null => Ok(None),
|
|
||||||
_otherwise => Err(AuthControllerError::InvalidApiKeyExpiresAt(value.clone())),
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,7 +1,5 @@
|
|||||||
mod action;
|
|
||||||
mod dump;
|
mod dump;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
mod key;
|
|
||||||
mod store;
|
mod store;
|
||||||
|
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
@ -9,14 +7,13 @@ use std::ops::Deref;
|
|||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use meilisearch_types::keys::{Action, Key};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use time::OffsetDateTime;
|
use time::OffsetDateTime;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
pub use action::{actions, Action};
|
|
||||||
use error::{AuthControllerError, Result};
|
use error::{AuthControllerError, Result};
|
||||||
pub use key::Key;
|
|
||||||
use meilisearch_types::star_or::StarOr;
|
use meilisearch_types::star_or::StarOr;
|
||||||
use store::generate_key_as_hexa;
|
use store::generate_key_as_hexa;
|
||||||
pub use store::open_auth_store_env;
|
pub use store::open_auth_store_env;
|
||||||
|
@ -10,6 +10,7 @@ use std::str;
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use hmac::{Hmac, Mac};
|
use hmac::{Hmac, Mac};
|
||||||
|
use meilisearch_types::keys::KeyId;
|
||||||
use meilisearch_types::star_or::StarOr;
|
use meilisearch_types::star_or::StarOr;
|
||||||
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};
|
||||||
@ -26,8 +27,6 @@ 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 = Uuid;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct HeedAuthStore {
|
pub struct HeedAuthStore {
|
||||||
env: Arc<Env>,
|
env: Arc<Env>,
|
||||||
|
@ -138,9 +138,9 @@ pub mod policies {
|
|||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::extractors::authentication::Policy;
|
use crate::extractors::authentication::Policy;
|
||||||
use meilisearch_auth::{Action, AuthController, AuthFilter, SearchRules};
|
use meilisearch_auth::{AuthController, AuthFilter, SearchRules};
|
||||||
// reexport actions in policies in order to be used in routes configuration.
|
// reexport actions in policies in order to be used in routes configuration.
|
||||||
pub use meilisearch_auth::actions;
|
pub use meilisearch_types::keys::{actions, Action};
|
||||||
|
|
||||||
fn tenant_token_validation() -> Validation {
|
fn tenant_token_validation() -> Validation {
|
||||||
let mut validation = Validation::default();
|
let mut validation = Validation::default();
|
||||||
|
@ -6,8 +6,9 @@ use serde_json::Value;
|
|||||||
use time::OffsetDateTime;
|
use time::OffsetDateTime;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use meilisearch_auth::{error::AuthControllerError, Action, AuthController, Key};
|
use meilisearch_auth::{error::AuthControllerError, AuthController};
|
||||||
use meilisearch_types::error::{Code, ResponseError};
|
use meilisearch_types::error::{Code, ResponseError};
|
||||||
|
use meilisearch_types::keys::{Action, Key};
|
||||||
|
|
||||||
use crate::extractors::{
|
use crate::extractors::{
|
||||||
authentication::{policies::*, GuardedData},
|
authentication::{policies::*, GuardedData},
|
||||||
|
@ -9,11 +9,13 @@ actix-web = { version = "4.2.1", default-features = false }
|
|||||||
csv = "1.1.6"
|
csv = "1.1.6"
|
||||||
either = { version = "1.6.1", features = ["serde"] }
|
either = { version = "1.6.1", features = ["serde"] }
|
||||||
milli = { git = "https://github.com/meilisearch/milli.git", tag = "v0.33.4", default-features = false }
|
milli = { git = "https://github.com/meilisearch/milli.git", tag = "v0.33.4", default-features = false }
|
||||||
|
enum-iterator = "0.7.0"
|
||||||
proptest = { version = "1.0.0", optional = true }
|
proptest = { version = "1.0.0", optional = true }
|
||||||
proptest-derive = { version = "0.3.0", optional = true }
|
proptest-derive = { version = "0.3.0", optional = true }
|
||||||
serde = { version = "1.0.145", features = ["derive"] }
|
serde = { version = "1.0.145", features = ["derive"] }
|
||||||
serde_json = "1.0.85"
|
serde_json = "1.0.85"
|
||||||
time = { version = "0.3.7", features = ["serde-well-known", "formatting", "parsing", "macros"] }
|
time = { version = "0.3.7", features = ["serde-well-known", "formatting", "parsing", "macros"] }
|
||||||
|
thiserror = "1.0.30"
|
||||||
tokio = "1.0"
|
tokio = "1.0"
|
||||||
uuid = { version = "1.1.2", features = ["serde", "v4"] }
|
uuid = { version = "1.1.2", features = ["serde", "v4"] }
|
||||||
|
|
||||||
|
366
meilisearch-types/src/keys.rs
Normal file
366
meilisearch-types/src/keys.rs
Normal file
@ -0,0 +1,366 @@
|
|||||||
|
use crate::error::{Code, ErrorCode};
|
||||||
|
use crate::index_uid::IndexUid;
|
||||||
|
use crate::star_or::StarOr;
|
||||||
|
use enum_iterator::IntoEnumIterator;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_json::{from_value, Value};
|
||||||
|
use std::hash::Hash;
|
||||||
|
use time::format_description::well_known::Rfc3339;
|
||||||
|
use time::macros::{format_description, time};
|
||||||
|
use time::{Date, OffsetDateTime, PrimitiveDateTime};
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
type Result<T> = std::result::Result<T, Error>;
|
||||||
|
|
||||||
|
pub type KeyId = Uuid;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Deserialize, Serialize)]
|
||||||
|
pub struct Key {
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub description: Option<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub name: Option<String>,
|
||||||
|
pub uid: KeyId,
|
||||||
|
pub actions: Vec<Action>,
|
||||||
|
pub indexes: Vec<StarOr<IndexUid>>,
|
||||||
|
#[serde(with = "time::serde::rfc3339::option")]
|
||||||
|
pub expires_at: Option<OffsetDateTime>,
|
||||||
|
#[serde(with = "time::serde::rfc3339")]
|
||||||
|
pub created_at: OffsetDateTime,
|
||||||
|
#[serde(with = "time::serde::rfc3339")]
|
||||||
|
pub updated_at: OffsetDateTime,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Key {
|
||||||
|
pub fn create_from_value(value: Value) -> Result<Self> {
|
||||||
|
let name = match value.get("name") {
|
||||||
|
None | Some(Value::Null) => None,
|
||||||
|
Some(des) => from_value(des.clone())
|
||||||
|
.map(Some)
|
||||||
|
.map_err(|_| Error::InvalidApiKeyName(des.clone()))?,
|
||||||
|
};
|
||||||
|
|
||||||
|
let description = match value.get("description") {
|
||||||
|
None | Some(Value::Null) => None,
|
||||||
|
Some(des) => from_value(des.clone())
|
||||||
|
.map(Some)
|
||||||
|
.map_err(|_| Error::InvalidApiKeyDescription(des.clone()))?,
|
||||||
|
};
|
||||||
|
|
||||||
|
let uid = value.get("uid").map_or_else(
|
||||||
|
|| Ok(Uuid::new_v4()),
|
||||||
|
|uid| from_value(uid.clone()).map_err(|_| Error::InvalidApiKeyUid(uid.clone())),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let actions = value
|
||||||
|
.get("actions")
|
||||||
|
.map(|act| {
|
||||||
|
from_value(act.clone()).map_err(|_| Error::InvalidApiKeyActions(act.clone()))
|
||||||
|
})
|
||||||
|
.ok_or(Error::MissingParameter("actions"))??;
|
||||||
|
|
||||||
|
let indexes = value
|
||||||
|
.get("indexes")
|
||||||
|
.map(|ind| {
|
||||||
|
from_value(ind.clone()).map_err(|_| Error::InvalidApiKeyIndexes(ind.clone()))
|
||||||
|
})
|
||||||
|
.ok_or(Error::MissingParameter("indexes"))??;
|
||||||
|
|
||||||
|
let expires_at = value
|
||||||
|
.get("expiresAt")
|
||||||
|
.map(parse_expiration_date)
|
||||||
|
.ok_or(Error::MissingParameter("expiresAt"))??;
|
||||||
|
|
||||||
|
let created_at = OffsetDateTime::now_utc();
|
||||||
|
let updated_at = created_at;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
uid,
|
||||||
|
actions,
|
||||||
|
indexes,
|
||||||
|
expires_at,
|
||||||
|
created_at,
|
||||||
|
updated_at,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_from_value(&mut self, value: Value) -> Result<()> {
|
||||||
|
if let Some(des) = value.get("description") {
|
||||||
|
let des =
|
||||||
|
from_value(des.clone()).map_err(|_| Error::InvalidApiKeyDescription(des.clone()));
|
||||||
|
self.description = des?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(des) = value.get("name") {
|
||||||
|
let des = from_value(des.clone()).map_err(|_| Error::InvalidApiKeyName(des.clone()));
|
||||||
|
self.name = des?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if value.get("uid").is_some() {
|
||||||
|
return Err(Error::ImmutableField("uid".to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if value.get("actions").is_some() {
|
||||||
|
return Err(Error::ImmutableField("actions".to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if value.get("indexes").is_some() {
|
||||||
|
return Err(Error::ImmutableField("indexes".to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if value.get("expiresAt").is_some() {
|
||||||
|
return Err(Error::ImmutableField("expiresAt".to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if value.get("createdAt").is_some() {
|
||||||
|
return Err(Error::ImmutableField("createdAt".to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if value.get("updatedAt").is_some() {
|
||||||
|
return Err(Error::ImmutableField("updatedAt".to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.updated_at = OffsetDateTime::now_utc();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn default_admin() -> Self {
|
||||||
|
let now = OffsetDateTime::now_utc();
|
||||||
|
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()),
|
||||||
|
uid,
|
||||||
|
actions: vec![Action::All],
|
||||||
|
indexes: vec![StarOr::Star],
|
||||||
|
expires_at: None,
|
||||||
|
created_at: now,
|
||||||
|
updated_at: now,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn default_search() -> Self {
|
||||||
|
let now = OffsetDateTime::now_utc();
|
||||||
|
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()),
|
||||||
|
uid,
|
||||||
|
actions: vec![Action::Search],
|
||||||
|
indexes: vec![StarOr::Star],
|
||||||
|
expires_at: None,
|
||||||
|
created_at: now,
|
||||||
|
updated_at: now,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_expiration_date(value: &Value) -> Result<Option<OffsetDateTime>> {
|
||||||
|
match value {
|
||||||
|
Value::String(string) => OffsetDateTime::parse(string, &Rfc3339)
|
||||||
|
.or_else(|_| {
|
||||||
|
PrimitiveDateTime::parse(
|
||||||
|
string,
|
||||||
|
format_description!(
|
||||||
|
"[year repr:full base:calendar]-[month repr:numerical]-[day]T[hour]:[minute]:[second]"
|
||||||
|
),
|
||||||
|
).map(|datetime| datetime.assume_utc())
|
||||||
|
})
|
||||||
|
.or_else(|_| {
|
||||||
|
PrimitiveDateTime::parse(
|
||||||
|
string,
|
||||||
|
format_description!(
|
||||||
|
"[year repr:full base:calendar]-[month repr:numerical]-[day] [hour]:[minute]:[second]"
|
||||||
|
),
|
||||||
|
).map(|datetime| datetime.assume_utc())
|
||||||
|
})
|
||||||
|
.or_else(|_| {
|
||||||
|
Date::parse(string, format_description!(
|
||||||
|
"[year repr:full base:calendar]-[month repr:numerical]-[day]"
|
||||||
|
)).map(|date| PrimitiveDateTime::new(date, time!(00:00)).assume_utc())
|
||||||
|
})
|
||||||
|
.map_err(|_| Error::InvalidApiKeyExpiresAt(value.clone()))
|
||||||
|
// check if the key is already expired.
|
||||||
|
.and_then(|d| {
|
||||||
|
if d > OffsetDateTime::now_utc() {
|
||||||
|
Ok(d)
|
||||||
|
} else {
|
||||||
|
Err(Error::InvalidApiKeyExpiresAt(value.clone()))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.map(Option::Some),
|
||||||
|
Value::Null => Ok(None),
|
||||||
|
_otherwise => Err(Error::InvalidApiKeyExpiresAt(value.clone())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(IntoEnumIterator, Copy, Clone, Serialize, Deserialize, Debug, Eq, PartialEq, Hash)]
|
||||||
|
#[repr(u8)]
|
||||||
|
pub enum Action {
|
||||||
|
#[serde(rename = "*")]
|
||||||
|
All = 0,
|
||||||
|
#[serde(rename = "search")]
|
||||||
|
Search,
|
||||||
|
#[serde(rename = "documents.*")]
|
||||||
|
DocumentsAll,
|
||||||
|
#[serde(rename = "documents.add")]
|
||||||
|
DocumentsAdd,
|
||||||
|
#[serde(rename = "documents.get")]
|
||||||
|
DocumentsGet,
|
||||||
|
#[serde(rename = "documents.delete")]
|
||||||
|
DocumentsDelete,
|
||||||
|
#[serde(rename = "indexes.*")]
|
||||||
|
IndexesAll,
|
||||||
|
#[serde(rename = "indexes.create")]
|
||||||
|
IndexesAdd,
|
||||||
|
#[serde(rename = "indexes.get")]
|
||||||
|
IndexesGet,
|
||||||
|
#[serde(rename = "indexes.update")]
|
||||||
|
IndexesUpdate,
|
||||||
|
#[serde(rename = "indexes.delete")]
|
||||||
|
IndexesDelete,
|
||||||
|
#[serde(rename = "tasks.*")]
|
||||||
|
TasksAll,
|
||||||
|
#[serde(rename = "tasks.get")]
|
||||||
|
TasksGet,
|
||||||
|
#[serde(rename = "settings.*")]
|
||||||
|
SettingsAll,
|
||||||
|
#[serde(rename = "settings.get")]
|
||||||
|
SettingsGet,
|
||||||
|
#[serde(rename = "settings.update")]
|
||||||
|
SettingsUpdate,
|
||||||
|
#[serde(rename = "stats.*")]
|
||||||
|
StatsAll,
|
||||||
|
#[serde(rename = "stats.get")]
|
||||||
|
StatsGet,
|
||||||
|
#[serde(rename = "metrics.*")]
|
||||||
|
MetricsAll,
|
||||||
|
#[serde(rename = "metrics.get")]
|
||||||
|
MetricsGet,
|
||||||
|
#[serde(rename = "dumps.*")]
|
||||||
|
DumpsAll,
|
||||||
|
#[serde(rename = "dumps.create")]
|
||||||
|
DumpsCreate,
|
||||||
|
#[serde(rename = "version")]
|
||||||
|
Version,
|
||||||
|
#[serde(rename = "keys.create")]
|
||||||
|
KeysAdd,
|
||||||
|
#[serde(rename = "keys.get")]
|
||||||
|
KeysGet,
|
||||||
|
#[serde(rename = "keys.update")]
|
||||||
|
KeysUpdate,
|
||||||
|
#[serde(rename = "keys.delete")]
|
||||||
|
KeysDelete,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Action {
|
||||||
|
pub const fn from_repr(repr: u8) -> Option<Self> {
|
||||||
|
use actions::*;
|
||||||
|
match repr {
|
||||||
|
ALL => Some(Self::All),
|
||||||
|
SEARCH => Some(Self::Search),
|
||||||
|
DOCUMENTS_ALL => Some(Self::DocumentsAll),
|
||||||
|
DOCUMENTS_ADD => Some(Self::DocumentsAdd),
|
||||||
|
DOCUMENTS_GET => Some(Self::DocumentsGet),
|
||||||
|
DOCUMENTS_DELETE => Some(Self::DocumentsDelete),
|
||||||
|
INDEXES_ALL => Some(Self::IndexesAll),
|
||||||
|
INDEXES_CREATE => Some(Self::IndexesAdd),
|
||||||
|
INDEXES_GET => Some(Self::IndexesGet),
|
||||||
|
INDEXES_UPDATE => Some(Self::IndexesUpdate),
|
||||||
|
INDEXES_DELETE => Some(Self::IndexesDelete),
|
||||||
|
TASKS_ALL => Some(Self::TasksAll),
|
||||||
|
TASKS_GET => Some(Self::TasksGet),
|
||||||
|
SETTINGS_ALL => Some(Self::SettingsAll),
|
||||||
|
SETTINGS_GET => Some(Self::SettingsGet),
|
||||||
|
SETTINGS_UPDATE => Some(Self::SettingsUpdate),
|
||||||
|
STATS_ALL => Some(Self::StatsAll),
|
||||||
|
STATS_GET => Some(Self::StatsGet),
|
||||||
|
METRICS_ALL => Some(Self::MetricsAll),
|
||||||
|
METRICS_GET => Some(Self::MetricsGet),
|
||||||
|
DUMPS_ALL => Some(Self::DumpsAll),
|
||||||
|
DUMPS_CREATE => Some(Self::DumpsCreate),
|
||||||
|
VERSION => Some(Self::Version),
|
||||||
|
KEYS_CREATE => Some(Self::KeysAdd),
|
||||||
|
KEYS_GET => Some(Self::KeysGet),
|
||||||
|
KEYS_UPDATE => Some(Self::KeysUpdate),
|
||||||
|
KEYS_DELETE => Some(Self::KeysDelete),
|
||||||
|
_otherwise => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn repr(&self) -> u8 {
|
||||||
|
*self as u8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod actions {
|
||||||
|
use super::Action::*;
|
||||||
|
|
||||||
|
pub(crate) const ALL: u8 = All.repr();
|
||||||
|
pub const SEARCH: u8 = Search.repr();
|
||||||
|
pub const DOCUMENTS_ALL: u8 = DocumentsAll.repr();
|
||||||
|
pub const DOCUMENTS_ADD: u8 = DocumentsAdd.repr();
|
||||||
|
pub const DOCUMENTS_GET: u8 = DocumentsGet.repr();
|
||||||
|
pub const DOCUMENTS_DELETE: u8 = DocumentsDelete.repr();
|
||||||
|
pub const INDEXES_ALL: u8 = IndexesAll.repr();
|
||||||
|
pub const INDEXES_CREATE: u8 = IndexesAdd.repr();
|
||||||
|
pub const INDEXES_GET: u8 = IndexesGet.repr();
|
||||||
|
pub const INDEXES_UPDATE: u8 = IndexesUpdate.repr();
|
||||||
|
pub const INDEXES_DELETE: u8 = IndexesDelete.repr();
|
||||||
|
pub const TASKS_ALL: u8 = TasksAll.repr();
|
||||||
|
pub const TASKS_GET: u8 = TasksGet.repr();
|
||||||
|
pub const SETTINGS_ALL: u8 = SettingsAll.repr();
|
||||||
|
pub const SETTINGS_GET: u8 = SettingsGet.repr();
|
||||||
|
pub const SETTINGS_UPDATE: u8 = SettingsUpdate.repr();
|
||||||
|
pub const STATS_ALL: u8 = StatsAll.repr();
|
||||||
|
pub const STATS_GET: u8 = StatsGet.repr();
|
||||||
|
pub const METRICS_ALL: u8 = MetricsAll.repr();
|
||||||
|
pub const METRICS_GET: u8 = MetricsGet.repr();
|
||||||
|
pub const DUMPS_ALL: u8 = DumpsAll.repr();
|
||||||
|
pub const DUMPS_CREATE: u8 = DumpsCreate.repr();
|
||||||
|
pub const VERSION: u8 = Version.repr();
|
||||||
|
pub const KEYS_CREATE: u8 = KeysAdd.repr();
|
||||||
|
pub const KEYS_GET: u8 = KeysGet.repr();
|
||||||
|
pub const KEYS_UPDATE: u8 = KeysUpdate.repr();
|
||||||
|
pub const KEYS_DELETE: u8 = KeysDelete.repr();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
pub enum Error {
|
||||||
|
#[error("`{0}` field is mandatory.")]
|
||||||
|
MissingParameter(&'static str),
|
||||||
|
#[error("`actions` field value `{0}` is invalid. It should be an array of string representing action names.")]
|
||||||
|
InvalidApiKeyActions(Value),
|
||||||
|
#[error("`indexes` field value `{0}` is invalid. It should be an array of string representing index names.")]
|
||||||
|
InvalidApiKeyIndexes(Value),
|
||||||
|
#[error("`expiresAt` field value `{0}` is invalid. It should follow the RFC 3339 format to represents a date or datetime in the future or specified as a null value. e.g. 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS'.")]
|
||||||
|
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 UUID v4 string or omitted.")]
|
||||||
|
InvalidApiKeyUid(Value),
|
||||||
|
#[error("The `{0}` field cannot be modified for the given resource.")]
|
||||||
|
ImmutableField(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ErrorCode for Error {
|
||||||
|
fn error_code(&self) -> Code {
|
||||||
|
match self {
|
||||||
|
Self::MissingParameter(_) => Code::MissingParameter,
|
||||||
|
Self::InvalidApiKeyActions(_) => Code::InvalidApiKeyActions,
|
||||||
|
Self::InvalidApiKeyIndexes(_) => Code::InvalidApiKeyIndexes,
|
||||||
|
Self::InvalidApiKeyExpiresAt(_) => Code::InvalidApiKeyExpiresAt,
|
||||||
|
Self::InvalidApiKeyDescription(_) => Code::InvalidApiKeyDescription,
|
||||||
|
Self::InvalidApiKeyName(_) => Code::InvalidApiKeyName,
|
||||||
|
Self::InvalidApiKeyUid(_) => Code::InvalidApiKeyUid,
|
||||||
|
Self::ImmutableField(_) => Code::ImmutableField,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
pub mod document_formats;
|
pub mod document_formats;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod index_uid;
|
pub mod index_uid;
|
||||||
|
pub mod keys;
|
||||||
pub mod settings;
|
pub mod settings;
|
||||||
pub mod star_or;
|
pub mod star_or;
|
||||||
pub mod tasks;
|
pub mod tasks;
|
||||||
|
Loading…
Reference in New Issue
Block a user