mirror of
https://github.com/meilisearch/MeiliSearch
synced 2025-07-04 04:17:10 +02:00
introduce new key management
This commit is contained in:
parent
5ac757a5fd
commit
257b7b4df4
18 changed files with 204 additions and 362 deletions
|
@ -7,6 +7,7 @@ use heed::types::{SerdeBincode, Str};
|
|||
use log::error;
|
||||
use meilisearch_core::{Database, Error as MError, MResult, MainT, UpdateT};
|
||||
use sysinfo::Pid;
|
||||
use sha2::Digest;
|
||||
|
||||
use crate::option::Opt;
|
||||
use crate::routes::index::index_update_callback;
|
||||
|
@ -32,10 +33,34 @@ impl Deref for Data {
|
|||
pub struct DataInner {
|
||||
pub db: Arc<Database>,
|
||||
pub db_path: String,
|
||||
pub api_key: Option<String>,
|
||||
pub api_keys: ApiKeys,
|
||||
pub server_pid: Pid,
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
pub struct ApiKeys {
|
||||
pub public: Option<String>,
|
||||
pub private: Option<String>,
|
||||
pub master: Option<String>,
|
||||
}
|
||||
|
||||
impl ApiKeys {
|
||||
pub fn generate_missing_api_keys(&mut self) {
|
||||
if let Some(master_key) = &self.master {
|
||||
if self.private.is_none() {
|
||||
let key = format!("{}-private", master_key);
|
||||
let sha = sha2::Sha256::digest(key.as_bytes());
|
||||
self.private = Some(format!("{:x}", sha));
|
||||
}
|
||||
if self.public.is_none() {
|
||||
let key = format!("{}-public", master_key);
|
||||
let sha = sha2::Sha256::digest(key.as_bytes());
|
||||
self.public = Some(format!("{:x}", sha));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DataInner {
|
||||
pub fn is_indexing(&self, reader: &heed::RoTxn<UpdateT>, index: &str) -> MResult<Option<bool>> {
|
||||
match self.db.open_index(&index) {
|
||||
|
@ -107,15 +132,22 @@ impl DataInner {
|
|||
impl Data {
|
||||
pub fn new(opt: Opt) -> Data {
|
||||
let db_path = opt.db_path.clone();
|
||||
let api_key = opt.api_key.clone();
|
||||
let server_pid = sysinfo::get_current_pid().unwrap();
|
||||
|
||||
let db = Arc::new(Database::open_or_create(opt.db_path).unwrap());
|
||||
|
||||
let mut api_keys = ApiKeys {
|
||||
master: opt.master_key.clone(),
|
||||
private: None,
|
||||
public: None,
|
||||
};
|
||||
|
||||
api_keys.generate_missing_api_keys();
|
||||
|
||||
let inner_data = DataInner {
|
||||
db: db.clone(),
|
||||
db_path,
|
||||
api_key,
|
||||
api_keys,
|
||||
server_pid,
|
||||
};
|
||||
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
use crate::error::{ResponseError, SResult};
|
||||
use crate::models::token::*;
|
||||
use crate::Data;
|
||||
use chrono::Utc;
|
||||
use heed::types::{SerdeBincode, Str};
|
||||
use meilisearch_core::Index;
|
||||
use tide::Request;
|
||||
|
||||
pub enum ACL {
|
||||
Admin,
|
||||
Private,
|
||||
Public
|
||||
}
|
||||
|
||||
pub trait RequestExt {
|
||||
fn is_allowed(&self, acl: ACL) -> SResult<()>;
|
||||
fn header(&self, name: &str) -> SResult<String>;
|
||||
fn url_param(&self, name: &str) -> SResult<String>;
|
||||
fn index(&self) -> SResult<Index>;
|
||||
fn identifier(&self) -> SResult<String>;
|
||||
|
@ -16,73 +18,36 @@ pub trait RequestExt {
|
|||
|
||||
impl RequestExt for Request<Data> {
|
||||
fn is_allowed(&self, acl: ACL) -> SResult<()> {
|
||||
let api_key = match &self.state().api_key {
|
||||
Some(api_key) => api_key,
|
||||
None => return Ok(()),
|
||||
};
|
||||
let user_api_key = self.header("X-Meili-API-Key");
|
||||
|
||||
let user_api_key = self
|
||||
.header("X-Meili-API-Key")
|
||||
.ok_or(ResponseError::missing_header("X-Meili-API-Key"))?;
|
||||
|
||||
if user_api_key == *api_key {
|
||||
return Ok(());
|
||||
}
|
||||
let request_index: Option<String> = None; //self.param::<String>("index").ok();
|
||||
|
||||
let db = &self.state().db;
|
||||
let reader = db.main_read_txn()?;
|
||||
|
||||
let token_key = format!("{}{}", TOKEN_PREFIX_KEY, user_api_key);
|
||||
|
||||
let token_config = db
|
||||
.common_store()
|
||||
.get::<_, Str, SerdeBincode<Token>>(&reader, &token_key)?
|
||||
.ok_or(ResponseError::invalid_token(format!(
|
||||
"Api key does not exist: {}",
|
||||
user_api_key
|
||||
)))?;
|
||||
|
||||
if token_config.revoked {
|
||||
return Err(ResponseError::invalid_token("token revoked"));
|
||||
}
|
||||
|
||||
if let Some(index) = request_index {
|
||||
if !token_config
|
||||
.indexes
|
||||
.iter()
|
||||
.any(|r| match_wildcard(&r, &index))
|
||||
{
|
||||
return Err(ResponseError::invalid_token(
|
||||
"token is not allowed to access to this index",
|
||||
));
|
||||
match acl {
|
||||
ACL::Admin => {
|
||||
if user_api_key == self.state().api_keys.master.as_deref() {
|
||||
return Ok(())
|
||||
}
|
||||
},
|
||||
ACL::Private => {
|
||||
if user_api_key == self.state().api_keys.master.as_deref() {
|
||||
return Ok(())
|
||||
}
|
||||
if user_api_key == self.state().api_keys.private.as_deref() {
|
||||
return Ok(())
|
||||
}
|
||||
},
|
||||
ACL::Public => {
|
||||
if user_api_key == self.state().api_keys.master.as_deref() {
|
||||
return Ok(())
|
||||
}
|
||||
if user_api_key == self.state().api_keys.private.as_deref() {
|
||||
return Ok(())
|
||||
}
|
||||
if user_api_key == self.state().api_keys.public.as_deref() {
|
||||
return Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if token_config.expires_at < Utc::now() {
|
||||
return Err(ResponseError::invalid_token("token expired"));
|
||||
}
|
||||
|
||||
if token_config.acl.contains(&ACL::All) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if !token_config.acl.contains(&acl) {
|
||||
return Err(ResponseError::invalid_token("no permission"));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn header(&self, name: &str) -> SResult<String> {
|
||||
let header = self
|
||||
.headers()
|
||||
.get(name)
|
||||
.ok_or(ResponseError::missing_header(name))?
|
||||
.to_str()
|
||||
.map_err(|_| ResponseError::missing_header("X-Meili-API-Key"))?
|
||||
.to_string();
|
||||
Ok(header)
|
||||
Err(ResponseError::InvalidToken(user_api_key.unwrap_or("Need a token").to_owned()))
|
||||
}
|
||||
|
||||
fn url_param(&self, name: &str) -> SResult<String> {
|
||||
|
|
|
@ -1,2 +1 @@
|
|||
pub mod token;
|
||||
pub mod update_operation;
|
||||
|
|
|
@ -1,72 +0,0 @@
|
|||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub const TOKEN_PREFIX_KEY: &str = "_token_";
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum ACL {
|
||||
IndexesRead,
|
||||
IndexesWrite,
|
||||
DocumentsRead,
|
||||
DocumentsWrite,
|
||||
SettingsRead,
|
||||
SettingsWrite,
|
||||
Admin,
|
||||
#[serde(rename = "*")]
|
||||
All,
|
||||
}
|
||||
|
||||
pub type Wildcard = String;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Token {
|
||||
pub key: String,
|
||||
pub description: String,
|
||||
pub acl: Vec<ACL>,
|
||||
pub indexes: Vec<Wildcard>,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub updated_at: DateTime<Utc>,
|
||||
pub expires_at: DateTime<Utc>,
|
||||
pub revoked: bool,
|
||||
}
|
||||
|
||||
fn cleanup_wildcard(input: &str) -> (bool, &str, bool) {
|
||||
let first = input.chars().next().filter(|&c| c == '*').is_some();
|
||||
let last = input.chars().last().filter(|&c| c == '*').is_some();
|
||||
let bound_last = std::cmp::max(input.len().saturating_sub(last as usize), first as usize);
|
||||
let output = input.get(first as usize..bound_last).unwrap();
|
||||
(first, output, last)
|
||||
}
|
||||
|
||||
pub fn match_wildcard(pattern: &str, input: &str) -> bool {
|
||||
let (first, pattern, last) = cleanup_wildcard(pattern);
|
||||
|
||||
match (first, last) {
|
||||
(false, false) => pattern == input,
|
||||
(true, false) => input.ends_with(pattern),
|
||||
(false, true) => input.starts_with(pattern),
|
||||
(true, true) => input.contains(pattern),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_match_wildcard() {
|
||||
assert!(match_wildcard("*", "qqq"));
|
||||
assert!(match_wildcard("*", ""));
|
||||
assert!(match_wildcard("*ab", "qqqab"));
|
||||
assert!(match_wildcard("*ab*", "qqqabqq"));
|
||||
assert!(match_wildcard("ab*", "abqqq"));
|
||||
assert!(match_wildcard("**", "ab"));
|
||||
assert!(match_wildcard("ab", "ab"));
|
||||
assert!(match_wildcard("ab*", "ab"));
|
||||
assert!(match_wildcard("*ab", "ab"));
|
||||
assert!(match_wildcard("*ab*", "ab"));
|
||||
assert!(match_wildcard("*😆*", "ab😆dsa"));
|
||||
}
|
||||
}
|
|
@ -11,8 +11,8 @@ pub struct Opt {
|
|||
pub http_addr: String,
|
||||
|
||||
/// The master key allowing you to do everything on the server.
|
||||
#[structopt(long, env = "MEILI_API_KEY")]
|
||||
pub api_key: Option<String>,
|
||||
#[structopt(long, env = "MEILI_MASTER_KEY")]
|
||||
pub master_key: Option<String>,
|
||||
|
||||
/// Do not send analytics to Meili.
|
||||
#[structopt(long, env = "MEILI_NO_ANALYTICS")]
|
||||
|
|
|
@ -8,11 +8,11 @@ use tide::{Request, Response};
|
|||
|
||||
use crate::error::{ResponseError, SResult};
|
||||
use crate::helpers::tide::RequestExt;
|
||||
use crate::models::token::ACL::*;
|
||||
use crate::helpers::tide::ACL::*;
|
||||
use crate::Data;
|
||||
|
||||
pub async fn get_document(ctx: Request<Data>) -> SResult<Response> {
|
||||
ctx.is_allowed(DocumentsRead)?;
|
||||
ctx.is_allowed(Public)?;
|
||||
|
||||
let index = ctx.index()?;
|
||||
|
||||
|
@ -40,7 +40,7 @@ pub struct IndexUpdateResponse {
|
|||
}
|
||||
|
||||
pub async fn delete_document(ctx: Request<Data>) -> SResult<Response> {
|
||||
ctx.is_allowed(DocumentsWrite)?;
|
||||
ctx.is_allowed(Private)?;
|
||||
|
||||
let index = ctx.index()?;
|
||||
let identifier = ctx.identifier()?;
|
||||
|
@ -66,7 +66,7 @@ struct BrowseQuery {
|
|||
}
|
||||
|
||||
pub async fn get_all_documents(ctx: Request<Data>) -> SResult<Response> {
|
||||
ctx.is_allowed(DocumentsRead)?;
|
||||
ctx.is_allowed(Private)?;
|
||||
|
||||
let index = ctx.index()?;
|
||||
let query: BrowseQuery = ctx.query().unwrap_or_default();
|
||||
|
@ -125,7 +125,7 @@ struct UpdateDocumentsQuery {
|
|||
}
|
||||
|
||||
async fn update_multiple_documents(mut ctx: Request<Data>, is_partial: bool) -> SResult<Response> {
|
||||
ctx.is_allowed(DocumentsWrite)?;
|
||||
ctx.is_allowed(Private)?;
|
||||
|
||||
let index = ctx.index()?;
|
||||
|
||||
|
@ -178,7 +178,7 @@ pub async fn add_or_update_multiple_documents(ctx: Request<Data>) -> SResult<Res
|
|||
}
|
||||
|
||||
pub async fn delete_multiple_documents(mut ctx: Request<Data>) -> SResult<Response> {
|
||||
ctx.is_allowed(DocumentsWrite)?;
|
||||
ctx.is_allowed(Private)?;
|
||||
|
||||
let data: Vec<Value> = ctx.body_json().await.map_err(ResponseError::bad_request)?;
|
||||
let index = ctx.index()?;
|
||||
|
@ -204,7 +204,7 @@ pub async fn delete_multiple_documents(mut ctx: Request<Data>) -> SResult<Respon
|
|||
}
|
||||
|
||||
pub async fn clear_all_documents(ctx: Request<Data>) -> SResult<Response> {
|
||||
ctx.is_allowed(DocumentsWrite)?;
|
||||
ctx.is_allowed(Private)?;
|
||||
|
||||
let index = ctx.index()?;
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::error::{ResponseError, SResult};
|
||||
use crate::helpers::tide::RequestExt;
|
||||
use crate::models::token::ACL::*;
|
||||
use crate::helpers::tide::ACL::*;
|
||||
use crate::Data;
|
||||
|
||||
use heed::types::{Str, Unit};
|
||||
|
|
|
@ -9,7 +9,7 @@ use tide::{Request, Response};
|
|||
|
||||
use crate::error::{IntoInternalError, ResponseError, SResult};
|
||||
use crate::helpers::tide::RequestExt;
|
||||
use crate::models::token::ACL::*;
|
||||
use crate::helpers::tide::ACL::*;
|
||||
use crate::Data;
|
||||
|
||||
fn generate_uid() -> String {
|
||||
|
@ -22,7 +22,7 @@ fn generate_uid() -> String {
|
|||
}
|
||||
|
||||
pub async fn list_indexes(ctx: Request<Data>) -> SResult<Response> {
|
||||
ctx.is_allowed(IndexesRead)?;
|
||||
ctx.is_allowed(Private)?;
|
||||
|
||||
let indexes_uids = ctx.state().db.indexes_uids();
|
||||
|
||||
|
@ -75,7 +75,7 @@ struct IndexResponse {
|
|||
}
|
||||
|
||||
pub async fn get_index(ctx: Request<Data>) -> SResult<Response> {
|
||||
ctx.is_allowed(IndexesRead)?;
|
||||
ctx.is_allowed(Private)?;
|
||||
|
||||
let index = ctx.index()?;
|
||||
|
||||
|
@ -122,7 +122,7 @@ struct IndexCreateResponse {
|
|||
}
|
||||
|
||||
pub async fn create_index(mut ctx: Request<Data>) -> SResult<Response> {
|
||||
ctx.is_allowed(IndexesWrite)?;
|
||||
ctx.is_allowed(Private)?;
|
||||
|
||||
let body = ctx
|
||||
.body_json::<IndexCreateRequest>()
|
||||
|
@ -201,7 +201,7 @@ struct UpdateIndexResponse {
|
|||
}
|
||||
|
||||
pub async fn update_index(mut ctx: Request<Data>) -> SResult<Response> {
|
||||
ctx.is_allowed(IndexesWrite)?;
|
||||
ctx.is_allowed(Private)?;
|
||||
|
||||
let body = ctx
|
||||
.body_json::<UpdateIndexRequest>()
|
||||
|
@ -250,7 +250,7 @@ pub async fn update_index(mut ctx: Request<Data>) -> SResult<Response> {
|
|||
}
|
||||
|
||||
pub async fn get_update_status(ctx: Request<Data>) -> SResult<Response> {
|
||||
ctx.is_allowed(IndexesRead)?;
|
||||
ctx.is_allowed(Private)?;
|
||||
|
||||
let db = &ctx.state().db;
|
||||
let reader = db.update_read_txn()?;
|
||||
|
@ -273,7 +273,7 @@ pub async fn get_update_status(ctx: Request<Data>) -> SResult<Response> {
|
|||
}
|
||||
|
||||
pub async fn get_all_updates_status(ctx: Request<Data>) -> SResult<Response> {
|
||||
ctx.is_allowed(IndexesRead)?;
|
||||
ctx.is_allowed(Private)?;
|
||||
let db = &ctx.state().db;
|
||||
let reader = db.update_read_txn()?;
|
||||
let index = ctx.index()?;
|
||||
|
@ -282,7 +282,7 @@ pub async fn get_all_updates_status(ctx: Request<Data>) -> SResult<Response> {
|
|||
}
|
||||
|
||||
pub async fn delete_index(ctx: Request<Data>) -> SResult<Response> {
|
||||
ctx.is_allowed(IndexesWrite)?;
|
||||
ctx.is_allowed(Private)?;
|
||||
let _ = ctx.index()?;
|
||||
let index_uid = ctx.url_param("index")?;
|
||||
ctx.state().db.delete_index(&index_uid)?;
|
||||
|
|
|
@ -1,172 +1,18 @@
|
|||
use chrono::serde::ts_seconds;
|
||||
use chrono::{DateTime, Utc};
|
||||
use heed::types::{SerdeBincode, Str};
|
||||
use rand::seq::SliceRandom;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tide::{Request, Response};
|
||||
|
||||
use crate::error::{ResponseError, SResult};
|
||||
use serde_json::json;
|
||||
use crate::error::SResult;
|
||||
use crate::helpers::tide::RequestExt;
|
||||
use crate::models::token::ACL::*;
|
||||
use crate::models::token::*;
|
||||
use crate::helpers::tide::ACL::*;
|
||||
use crate::Data;
|
||||
|
||||
fn generate_api_key() -> String {
|
||||
let mut rng = rand::thread_rng();
|
||||
let sample = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||
sample
|
||||
.choose_multiple(&mut rng, 40)
|
||||
.map(|c| *c as char)
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub async fn list(ctx: Request<Data>) -> SResult<Response> {
|
||||
ctx.is_allowed(Admin)?;
|
||||
|
||||
let db = &ctx.state().db;
|
||||
let reader = db.main_read_txn()?;
|
||||
let keys = &ctx.state().api_keys;
|
||||
|
||||
let common_store = db.common_store();
|
||||
|
||||
let mut response: Vec<Token> = Vec::new();
|
||||
|
||||
let iter =
|
||||
common_store.prefix_iter::<_, Str, SerdeBincode<Token>>(&reader, TOKEN_PREFIX_KEY)?;
|
||||
|
||||
for result in iter {
|
||||
let (_, token) = result?;
|
||||
response.push(token);
|
||||
}
|
||||
|
||||
Ok(tide::Response::new(200).body_json(&response).unwrap())
|
||||
}
|
||||
|
||||
pub async fn get(ctx: Request<Data>) -> SResult<Response> {
|
||||
ctx.is_allowed(Admin)?;
|
||||
let request_key = ctx.url_param("key")?;
|
||||
|
||||
let db = &ctx.state().db;
|
||||
let reader = db.main_read_txn()?;
|
||||
|
||||
let token_key = format!("{}{}", TOKEN_PREFIX_KEY, request_key);
|
||||
|
||||
let token_config = db
|
||||
.common_store()
|
||||
.get::<_, Str, SerdeBincode<Token>>(&reader, &token_key)?
|
||||
.ok_or(ResponseError::not_found(format!(
|
||||
"token key: {}",
|
||||
token_key
|
||||
)))?;
|
||||
|
||||
Ok(tide::Response::new(200).body_json(&token_config).unwrap())
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||
pub struct CreatedRequest {
|
||||
description: String,
|
||||
acl: Vec<ACL>,
|
||||
indexes: Vec<Wildcard>,
|
||||
#[serde(with = "ts_seconds")]
|
||||
expires_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
pub async fn create(mut ctx: Request<Data>) -> SResult<Response> {
|
||||
ctx.is_allowed(Admin)?;
|
||||
|
||||
let data: CreatedRequest = ctx.body_json().await.map_err(ResponseError::bad_request)?;
|
||||
|
||||
let key = generate_api_key();
|
||||
let token_key = format!("{}{}", TOKEN_PREFIX_KEY, key);
|
||||
|
||||
let token_definition = Token {
|
||||
key,
|
||||
description: data.description,
|
||||
acl: data.acl,
|
||||
indexes: data.indexes,
|
||||
expires_at: data.expires_at,
|
||||
created_at: Utc::now(),
|
||||
updated_at: Utc::now(),
|
||||
revoked: false,
|
||||
};
|
||||
|
||||
let db = &ctx.state().db;
|
||||
let mut writer = db.main_write_txn()?;
|
||||
|
||||
db.common_store().put::<_, Str, SerdeBincode<Token>>(
|
||||
&mut writer,
|
||||
&token_key,
|
||||
&token_definition,
|
||||
)?;
|
||||
|
||||
writer.commit()?;
|
||||
Ok(tide::Response::new(201)
|
||||
.body_json(&token_definition)
|
||||
.unwrap())
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||
pub struct UpdatedRequest {
|
||||
description: Option<String>,
|
||||
acl: Option<Vec<ACL>>,
|
||||
indexes: Option<Vec<Wildcard>>,
|
||||
expires_at: Option<DateTime<Utc>>,
|
||||
revoked: Option<bool>,
|
||||
}
|
||||
|
||||
pub async fn update(mut ctx: Request<Data>) -> SResult<Response> {
|
||||
ctx.is_allowed(Admin)?;
|
||||
let request_key = ctx.url_param("key")?;
|
||||
|
||||
let data: UpdatedRequest = ctx.body_json().await.map_err(ResponseError::bad_request)?;
|
||||
|
||||
let db = &ctx.state().db;
|
||||
let mut writer = db.main_write_txn()?;
|
||||
|
||||
let common_store = db.common_store();
|
||||
|
||||
let token_key = format!("{}{}", TOKEN_PREFIX_KEY, request_key);
|
||||
|
||||
let mut token_config = common_store
|
||||
.get::<_, Str, SerdeBincode<Token>>(&writer, &token_key)?
|
||||
.ok_or(ResponseError::not_found(format!(
|
||||
"token key: {}",
|
||||
token_key
|
||||
)))?;
|
||||
|
||||
// apply the modifications
|
||||
if let Some(description) = data.description {
|
||||
token_config.description = description;
|
||||
}
|
||||
if let Some(acl) = data.acl {
|
||||
token_config.acl = acl;
|
||||
}
|
||||
if let Some(indexes) = data.indexes {
|
||||
token_config.indexes = indexes;
|
||||
}
|
||||
if let Some(expires_at) = data.expires_at {
|
||||
token_config.expires_at = expires_at;
|
||||
}
|
||||
if let Some(revoked) = data.revoked {
|
||||
token_config.revoked = revoked;
|
||||
}
|
||||
|
||||
token_config.updated_at = Utc::now();
|
||||
common_store.put::<_, Str, SerdeBincode<Token>>(&mut writer, &token_key, &token_config)?;
|
||||
writer.commit()?;
|
||||
|
||||
Ok(tide::Response::new(200).body_json(&token_config).unwrap())
|
||||
}
|
||||
|
||||
pub async fn delete(ctx: Request<Data>) -> SResult<Response> {
|
||||
ctx.is_allowed(Admin)?;
|
||||
let request_key = ctx.url_param("key")?;
|
||||
let db = &ctx.state().db;
|
||||
let mut writer = db.main_write_txn()?;
|
||||
let common_store = db.common_store();
|
||||
let token_key = format!("{}{}", TOKEN_PREFIX_KEY, request_key);
|
||||
common_store.delete::<_, Str>(&mut writer, &token_key)?;
|
||||
writer.commit()?;
|
||||
Ok(tide::Response::new(204))
|
||||
Ok(tide::Response::new(200)
|
||||
.body_json(&json!({
|
||||
"private": keys.private,
|
||||
"public": keys.public,
|
||||
}))?)
|
||||
}
|
||||
|
|
|
@ -118,13 +118,7 @@ pub fn load_routes(app: &mut tide::Server<Data>) {
|
|||
.get(|ctx| into_response(stats::index_stats(ctx)));
|
||||
|
||||
app.at("/keys/")
|
||||
.get(|ctx| into_response(key::list(ctx)))
|
||||
.post(|ctx| into_response(key::create(ctx)));
|
||||
|
||||
app.at("/keys/:key")
|
||||
.get(|ctx| into_response(key::get(ctx)))
|
||||
.put(|ctx| into_response(key::update(ctx)))
|
||||
.delete(|ctx| into_response(key::delete(ctx)));
|
||||
.get(|ctx| into_response(key::list(ctx)));
|
||||
|
||||
app.at("/health")
|
||||
.get(|ctx| into_response(health::get_health(ctx)))
|
||||
|
|
|
@ -7,6 +7,7 @@ use rayon::iter::{IntoParallelIterator, ParallelIterator};
|
|||
use serde::{Deserialize, Serialize};
|
||||
use tide::{Request, Response};
|
||||
|
||||
use crate::helpers::tide::ACL::*;
|
||||
use crate::error::{ResponseError, SResult};
|
||||
use crate::helpers::meilisearch::{Error, IndexSearchExt, SearchHit};
|
||||
use crate::helpers::tide::RequestExt;
|
||||
|
@ -28,7 +29,7 @@ struct SearchQuery {
|
|||
}
|
||||
|
||||
pub async fn search_with_url_query(ctx: Request<Data>) -> SResult<Response> {
|
||||
// ctx.is_allowed(DocumentsRead)?;
|
||||
ctx.is_allowed(Public)?;
|
||||
|
||||
let index = ctx.index()?;
|
||||
let db = &ctx.state().db;
|
||||
|
@ -143,7 +144,7 @@ struct SearchMultiBodyResponse {
|
|||
}
|
||||
|
||||
pub async fn search_multi_index(mut ctx: Request<Data>) -> SResult<Response> {
|
||||
// ctx.is_allowed(DocumentsRead)?;
|
||||
ctx.is_allowed(Public)?;
|
||||
let body = ctx
|
||||
.body_json::<SearchMultiBody>()
|
||||
.await
|
||||
|
|
|
@ -5,12 +5,12 @@ use tide::{Request, Response};
|
|||
|
||||
use crate::error::{ResponseError, SResult};
|
||||
use crate::helpers::tide::RequestExt;
|
||||
use crate::models::token::ACL::*;
|
||||
use crate::helpers::tide::ACL::*;
|
||||
use crate::routes::document::IndexUpdateResponse;
|
||||
use crate::Data;
|
||||
|
||||
pub async fn get_all(ctx: Request<Data>) -> SResult<Response> {
|
||||
ctx.is_allowed(SettingsRead)?;
|
||||
ctx.is_allowed(Private)?;
|
||||
let index = ctx.index()?;
|
||||
let db = &ctx.state().db;
|
||||
let reader = db.main_read_txn()?;
|
||||
|
@ -106,7 +106,7 @@ pub struct UpdateSettings {
|
|||
}
|
||||
|
||||
pub async fn update_all(mut ctx: Request<Data>) -> SResult<Response> {
|
||||
ctx.is_allowed(SettingsWrite)?;
|
||||
ctx.is_allowed(Private)?;
|
||||
let index = ctx.index()?;
|
||||
let settings_update: UpdateSettings =
|
||||
ctx.body_json().await.map_err(ResponseError::bad_request)?;
|
||||
|
@ -131,7 +131,7 @@ pub async fn update_all(mut ctx: Request<Data>) -> SResult<Response> {
|
|||
}
|
||||
|
||||
pub async fn delete_all(ctx: Request<Data>) -> SResult<Response> {
|
||||
ctx.is_allowed(SettingsWrite)?;
|
||||
ctx.is_allowed(Private)?;
|
||||
let index = ctx.index()?;
|
||||
let db = &ctx.state().db;
|
||||
let mut writer = db.update_write_txn()?;
|
||||
|
@ -156,7 +156,7 @@ pub async fn delete_all(ctx: Request<Data>) -> SResult<Response> {
|
|||
}
|
||||
|
||||
pub async fn get_rules(ctx: Request<Data>) -> SResult<Response> {
|
||||
ctx.is_allowed(SettingsRead)?;
|
||||
ctx.is_allowed(Private)?;
|
||||
let index = ctx.index()?;
|
||||
let db = &ctx.state().db;
|
||||
let reader = db.main_read_txn()?;
|
||||
|
@ -170,7 +170,7 @@ pub async fn get_rules(ctx: Request<Data>) -> SResult<Response> {
|
|||
}
|
||||
|
||||
pub async fn update_rules(mut ctx: Request<Data>) -> SResult<Response> {
|
||||
ctx.is_allowed(SettingsWrite)?;
|
||||
ctx.is_allowed(Private)?;
|
||||
let index = ctx.index()?;
|
||||
let ranking_rules: Option<Vec<String>> =
|
||||
ctx.body_json().await.map_err(ResponseError::bad_request)?;
|
||||
|
@ -190,7 +190,7 @@ pub async fn update_rules(mut ctx: Request<Data>) -> SResult<Response> {
|
|||
}
|
||||
|
||||
pub async fn delete_rules(ctx: Request<Data>) -> SResult<Response> {
|
||||
ctx.is_allowed(SettingsWrite)?;
|
||||
ctx.is_allowed(Private)?;
|
||||
let index = ctx.index()?;
|
||||
let db = &ctx.state().db;
|
||||
let mut writer = db.update_write_txn()?;
|
||||
|
@ -209,7 +209,7 @@ pub async fn delete_rules(ctx: Request<Data>) -> SResult<Response> {
|
|||
}
|
||||
|
||||
pub async fn get_distinct(ctx: Request<Data>) -> SResult<Response> {
|
||||
ctx.is_allowed(SettingsRead)?;
|
||||
ctx.is_allowed(Private)?;
|
||||
let index = ctx.index()?;
|
||||
let db = &ctx.state().db;
|
||||
let reader = db.main_read_txn()?;
|
||||
|
@ -222,7 +222,7 @@ pub async fn get_distinct(ctx: Request<Data>) -> SResult<Response> {
|
|||
}
|
||||
|
||||
pub async fn update_distinct(mut ctx: Request<Data>) -> SResult<Response> {
|
||||
ctx.is_allowed(SettingsWrite)?;
|
||||
ctx.is_allowed(Private)?;
|
||||
let index = ctx.index()?;
|
||||
let ranking_distinct: Option<String> =
|
||||
ctx.body_json().await.map_err(ResponseError::bad_request)?;
|
||||
|
@ -242,7 +242,7 @@ pub async fn update_distinct(mut ctx: Request<Data>) -> SResult<Response> {
|
|||
}
|
||||
|
||||
pub async fn delete_distinct(ctx: Request<Data>) -> SResult<Response> {
|
||||
ctx.is_allowed(SettingsWrite)?;
|
||||
ctx.is_allowed(Private)?;
|
||||
let index = ctx.index()?;
|
||||
let db = &ctx.state().db;
|
||||
let mut writer = db.update_write_txn()?;
|
||||
|
@ -261,7 +261,7 @@ pub async fn delete_distinct(ctx: Request<Data>) -> SResult<Response> {
|
|||
}
|
||||
|
||||
pub async fn get_identifier(ctx: Request<Data>) -> SResult<Response> {
|
||||
ctx.is_allowed(SettingsRead)?;
|
||||
ctx.is_allowed(Private)?;
|
||||
let index = ctx.index()?;
|
||||
let db = &ctx.state().db;
|
||||
let reader = db.main_read_txn()?;
|
||||
|
@ -274,7 +274,7 @@ pub async fn get_identifier(ctx: Request<Data>) -> SResult<Response> {
|
|||
}
|
||||
|
||||
pub async fn get_searchable(ctx: Request<Data>) -> SResult<Response> {
|
||||
ctx.is_allowed(SettingsRead)?;
|
||||
ctx.is_allowed(Private)?;
|
||||
let index = ctx.index()?;
|
||||
let db = &ctx.state().db;
|
||||
let reader = db.main_read_txn()?;
|
||||
|
@ -290,7 +290,7 @@ pub async fn get_searchable(ctx: Request<Data>) -> SResult<Response> {
|
|||
}
|
||||
|
||||
pub async fn update_searchable(mut ctx: Request<Data>) -> SResult<Response> {
|
||||
ctx.is_allowed(SettingsWrite)?;
|
||||
ctx.is_allowed(Private)?;
|
||||
let index = ctx.index()?;
|
||||
let searchable_attributes: Option<Vec<String>> =
|
||||
ctx.body_json().await.map_err(ResponseError::bad_request)?;
|
||||
|
@ -310,7 +310,7 @@ pub async fn update_searchable(mut ctx: Request<Data>) -> SResult<Response> {
|
|||
}
|
||||
|
||||
pub async fn delete_searchable(ctx: Request<Data>) -> SResult<Response> {
|
||||
ctx.is_allowed(SettingsWrite)?;
|
||||
ctx.is_allowed(Private)?;
|
||||
let index = ctx.index()?;
|
||||
let db = &ctx.state().db;
|
||||
|
||||
|
@ -328,7 +328,7 @@ pub async fn delete_searchable(ctx: Request<Data>) -> SResult<Response> {
|
|||
}
|
||||
|
||||
pub async fn displayed(ctx: Request<Data>) -> SResult<Response> {
|
||||
ctx.is_allowed(SettingsRead)?;
|
||||
ctx.is_allowed(Private)?;
|
||||
let index = ctx.index()?;
|
||||
let db = &ctx.state().db;
|
||||
let reader = db.main_read_txn()?;
|
||||
|
@ -348,7 +348,7 @@ pub async fn displayed(ctx: Request<Data>) -> SResult<Response> {
|
|||
}
|
||||
|
||||
pub async fn update_displayed(mut ctx: Request<Data>) -> SResult<Response> {
|
||||
ctx.is_allowed(SettingsWrite)?;
|
||||
ctx.is_allowed(Private)?;
|
||||
let index = ctx.index()?;
|
||||
let displayed_attributes: Option<HashSet<String>> =
|
||||
ctx.body_json().await.map_err(ResponseError::bad_request)?;
|
||||
|
@ -368,7 +368,7 @@ pub async fn update_displayed(mut ctx: Request<Data>) -> SResult<Response> {
|
|||
}
|
||||
|
||||
pub async fn delete_displayed(ctx: Request<Data>) -> SResult<Response> {
|
||||
ctx.is_allowed(SettingsWrite)?;
|
||||
ctx.is_allowed(Private)?;
|
||||
let index = ctx.index()?;
|
||||
let db = &ctx.state().db;
|
||||
|
||||
|
@ -386,7 +386,7 @@ pub async fn delete_displayed(ctx: Request<Data>) -> SResult<Response> {
|
|||
}
|
||||
|
||||
pub async fn get_index_new_fields(ctx: Request<Data>) -> SResult<Response> {
|
||||
ctx.is_allowed(SettingsRead)?;
|
||||
ctx.is_allowed(Private)?;
|
||||
let index = ctx.index()?;
|
||||
let db = &ctx.state().db;
|
||||
let reader = db.main_read_txn()?;
|
||||
|
@ -401,7 +401,7 @@ pub async fn get_index_new_fields(ctx: Request<Data>) -> SResult<Response> {
|
|||
}
|
||||
|
||||
pub async fn update_index_new_fields(mut ctx: Request<Data>) -> SResult<Response> {
|
||||
ctx.is_allowed(SettingsWrite)?;
|
||||
ctx.is_allowed(Private)?;
|
||||
let index = ctx.index()?;
|
||||
let index_new_fields: Option<bool> =
|
||||
ctx.body_json().await.map_err(ResponseError::bad_request)?;
|
||||
|
|
|
@ -10,7 +10,7 @@ use walkdir::WalkDir;
|
|||
|
||||
use crate::error::{IntoInternalError, SResult};
|
||||
use crate::helpers::tide::RequestExt;
|
||||
use crate::models::token::ACL::*;
|
||||
use crate::helpers::tide::ACL::*;
|
||||
use crate::Data;
|
||||
|
||||
#[derive(Serialize)]
|
||||
|
|
|
@ -5,12 +5,12 @@ use tide::{Request, Response};
|
|||
|
||||
use crate::error::{ResponseError, SResult};
|
||||
use crate::helpers::tide::RequestExt;
|
||||
use crate::models::token::ACL::*;
|
||||
use crate::helpers::tide::ACL::*;
|
||||
use crate::routes::document::IndexUpdateResponse;
|
||||
use crate::Data;
|
||||
|
||||
pub async fn get(ctx: Request<Data>) -> SResult<Response> {
|
||||
ctx.is_allowed(SettingsRead)?;
|
||||
ctx.is_allowed(Private)?;
|
||||
let index = ctx.index()?;
|
||||
let db = &ctx.state().db;
|
||||
let reader = db.main_read_txn()?;
|
||||
|
@ -21,7 +21,7 @@ pub async fn get(ctx: Request<Data>) -> SResult<Response> {
|
|||
}
|
||||
|
||||
pub async fn update(mut ctx: Request<Data>) -> SResult<Response> {
|
||||
ctx.is_allowed(SettingsRead)?;
|
||||
ctx.is_allowed(Private)?;
|
||||
let index = ctx.index()?;
|
||||
|
||||
let data: BTreeSet<String> = ctx.body_json().await.map_err(ResponseError::bad_request)?;
|
||||
|
@ -43,7 +43,7 @@ pub async fn update(mut ctx: Request<Data>) -> SResult<Response> {
|
|||
}
|
||||
|
||||
pub async fn delete(ctx: Request<Data>) -> SResult<Response> {
|
||||
ctx.is_allowed(SettingsRead)?;
|
||||
ctx.is_allowed(Private)?;
|
||||
let index = ctx.index()?;
|
||||
|
||||
let db = &ctx.state().db;
|
||||
|
|
|
@ -6,12 +6,12 @@ use tide::{Request, Response};
|
|||
|
||||
use crate::error::{ResponseError, SResult};
|
||||
use crate::helpers::tide::RequestExt;
|
||||
use crate::models::token::ACL::*;
|
||||
use crate::helpers::tide::ACL::*;
|
||||
use crate::routes::document::IndexUpdateResponse;
|
||||
use crate::Data;
|
||||
|
||||
pub async fn get(ctx: Request<Data>) -> SResult<Response> {
|
||||
ctx.is_allowed(SettingsRead)?;
|
||||
ctx.is_allowed(Private)?;
|
||||
let index = ctx.index()?;
|
||||
|
||||
let db = &ctx.state().db;
|
||||
|
@ -37,7 +37,7 @@ pub async fn get(ctx: Request<Data>) -> SResult<Response> {
|
|||
}
|
||||
|
||||
pub async fn update(mut ctx: Request<Data>) -> SResult<Response> {
|
||||
ctx.is_allowed(SettingsWrite)?;
|
||||
ctx.is_allowed(Private)?;
|
||||
|
||||
let data: BTreeMap<String, Vec<String>> =
|
||||
ctx.body_json().await.map_err(ResponseError::bad_request)?;
|
||||
|
@ -61,7 +61,7 @@ pub async fn update(mut ctx: Request<Data>) -> SResult<Response> {
|
|||
}
|
||||
|
||||
pub async fn delete(ctx: Request<Data>) -> SResult<Response> {
|
||||
ctx.is_allowed(SettingsWrite)?;
|
||||
ctx.is_allowed(Private)?;
|
||||
|
||||
let index = ctx.index()?;
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue