feat(auth): Extend API keys

- Add API keys in snapshots
- Add API keys in dumps
- Rename action indexes.add to indexes.create
- fix QA #1979

fix #1979
fix #1995
fix #2001
fix #2003
related to #1890
This commit is contained in:
many 2021-12-06 15:45:41 +01:00 committed by Maxime Legendre
parent 8096b568f0
commit ee7970f603
19 changed files with 418 additions and 204 deletions

View file

@ -14,8 +14,8 @@ pub enum Action {
DocumentsGet = actions::DOCUMENTS_GET,
#[serde(rename = "documents.delete")]
DocumentsDelete = actions::DOCUMENTS_DELETE,
#[serde(rename = "indexes.add")]
IndexesAdd = actions::INDEXES_ADD,
#[serde(rename = "indexes.create")]
IndexesAdd = actions::INDEXES_CREATE,
#[serde(rename = "indexes.get")]
IndexesGet = actions::INDEXES_GET,
#[serde(rename = "indexes.update")]
@ -47,7 +47,7 @@ impl Action {
DOCUMENTS_ADD => Some(Self::DocumentsAdd),
DOCUMENTS_GET => Some(Self::DocumentsGet),
DOCUMENTS_DELETE => Some(Self::DocumentsDelete),
INDEXES_ADD => Some(Self::IndexesAdd),
INDEXES_CREATE => Some(Self::IndexesAdd),
INDEXES_GET => Some(Self::IndexesGet),
INDEXES_UPDATE => Some(Self::IndexesUpdate),
INDEXES_DELETE => Some(Self::IndexesDelete),
@ -70,7 +70,7 @@ impl Action {
Self::DocumentsAdd => DOCUMENTS_ADD,
Self::DocumentsGet => DOCUMENTS_GET,
Self::DocumentsDelete => DOCUMENTS_DELETE,
Self::IndexesAdd => INDEXES_ADD,
Self::IndexesAdd => INDEXES_CREATE,
Self::IndexesGet => INDEXES_GET,
Self::IndexesUpdate => INDEXES_UPDATE,
Self::IndexesDelete => INDEXES_DELETE,
@ -90,7 +90,7 @@ pub mod actions {
pub const DOCUMENTS_ADD: u8 = 2;
pub const DOCUMENTS_GET: u8 = 3;
pub const DOCUMENTS_DELETE: u8 = 4;
pub const INDEXES_ADD: u8 = 5;
pub const INDEXES_CREATE: u8 = 5;
pub const INDEXES_GET: u8 = 6;
pub const INDEXES_UPDATE: u8 = 7;
pub const INDEXES_DELETE: u8 = 8;

View file

@ -0,0 +1,40 @@
use std::fs::File;
use std::io::BufRead;
use std::io::BufReader;
use std::io::Write;
use std::path::Path;
use crate::{AuthController, HeedAuthStore, Result};
const KEYS_PATH: &str = "keys";
impl AuthController {
pub fn dump(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> Result<()> {
let store = HeedAuthStore::new(&src)?;
let keys_file_path = dst.as_ref().join(KEYS_PATH);
let keys = store.list_api_keys()?;
let mut keys_file = File::create(&keys_file_path)?;
for key in keys {
serde_json::to_writer(&mut keys_file, &key)?;
keys_file.write_all(b"\n")?;
}
Ok(())
}
pub fn load_dump(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> Result<()> {
let store = HeedAuthStore::new(&dst)?;
let keys_file_path = src.as_ref().join(KEYS_PATH);
let mut reader = BufReader::new(File::open(&keys_file_path)?).lines();
while let Some(key) = reader.next().transpose()? {
let key = serde_json::from_str(&key)?;
store.put_api_key(key)?;
}
Ok(())
}
}

View file

@ -24,7 +24,12 @@ pub enum AuthControllerError {
Internal(Box<dyn Error + Send + Sync + 'static>),
}
internal_error!(AuthControllerError: heed::Error, std::io::Error);
internal_error!(
AuthControllerError: heed::Error,
std::io::Error,
serde_json::Error,
std::str::Utf8Error
);
impl ErrorCode for AuthControllerError {
fn error_code(&self) -> Code {

View file

@ -1,7 +1,7 @@
use crate::action::Action;
use crate::error::{AuthControllerError, Result};
use crate::store::{KeyId, KEY_ID_LENGTH};
use chrono::{DateTime, Utc};
use chrono::{DateTime, NaiveDateTime, Utc};
use rand::Rng;
use serde::{Deserialize, Serialize};
use serde_json::{from_value, Value};
@ -48,11 +48,8 @@ impl Key {
let expires_at = value
.get("expiresAt")
.map(|exp| {
from_value(exp.clone())
.map_err(|_| AuthControllerError::InvalidApiKeyExpiresAt(exp.clone()))
})
.transpose()?;
.map(parse_expiration_date)
.ok_or(AuthControllerError::MissingParameter("expiresAt"))??;
let created_at = Utc::now();
let updated_at = Utc::now();
@ -88,9 +85,7 @@ impl Key {
}
if let Some(exp) = value.get("expiresAt") {
let exp = from_value(exp.clone())
.map_err(|_| AuthControllerError::InvalidApiKeyExpiresAt(exp.clone()));
self.expires_at = exp?;
self.expires_at = parse_expiration_date(exp)?;
}
self.updated_at = Utc::now();
@ -137,3 +132,30 @@ fn generate_id() -> [u8; KEY_ID_LENGTH] {
bytes
}
fn parse_expiration_date(value: &Value) -> Result<Option<DateTime<Utc>>> {
match value {
Value::String(string) => DateTime::parse_from_rfc3339(string)
.map(|d| d.into())
.or_else(|_| {
NaiveDateTime::parse_from_str(string, "%Y-%m-%dT%H:%M:%S")
.map(|naive| DateTime::from_utc(naive, Utc))
})
.or_else(|_| {
NaiveDateTime::parse_from_str(string, "%Y-%m-%d")
.map(|naive| DateTime::from_utc(naive, Utc))
})
.map_err(|_| AuthControllerError::InvalidApiKeyExpiresAt(value.clone()))
// check if the key is already expired.
.and_then(|d| {
if d > Utc::now() {
Ok(d)
} else {
Err(AuthControllerError::InvalidApiKeyExpiresAt(value.clone()))
}
})
.map(Option::Some),
Value::Null => Ok(None),
_otherwise => Err(AuthControllerError::InvalidApiKeyExpiresAt(value.clone())),
}
}

View file

@ -1,4 +1,5 @@
mod action;
mod dump;
pub mod error;
mod key;
mod store;
@ -104,7 +105,7 @@ impl AuthController {
None => self.store.prefix_first_expiration_date(token, action)?,
})
{
let id = from_utf8(&id).map_err(|e| AuthControllerError::Internal(Box::new(e)))?;
let id = from_utf8(&id)?;
if exp.map_or(true, |exp| Utc::now() < exp)
&& generate_key(master_key.as_bytes(), id).as_bytes() == token
{

View file

@ -1,5 +1,6 @@
use enum_iterator::IntoEnumIterator;
use std::borrow::Cow;
use std::cmp::Reverse;
use std::convert::TryFrom;
use std::convert::TryInto;
use std::fs::create_dir_all;
@ -121,6 +122,7 @@ impl HeedAuthStore {
let (_, content) = result?;
list.push(content);
}
list.sort_unstable_by_key(|k| Reverse(k.created_at));
Ok(list)
}