104 lines
3 KiB
Rust
104 lines
3 KiB
Rust
use crate::macros::hex;
|
|
#[cfg(test)]
|
|
use crate::test::SNAKEOIL_HASH;
|
|
use rocket::{
|
|
http::Status,
|
|
request::{FromRequest, Outcome, Request},
|
|
};
|
|
use sha3::{Digest, Sha3_256};
|
|
|
|
/// Generate an API Key and its hash
|
|
pub fn genkey() -> (String, String) {
|
|
let apikey = {
|
|
let bytes: Vec<u8> = (0..36).map(|_| rand::random::<u8>()).collect();
|
|
base64::encode(bytes).replace('+', "-").replace('/', "_")
|
|
};
|
|
let apihash = {
|
|
let mut hasher = Sha3_256::default();
|
|
hasher.update(&apikey);
|
|
hex!(hasher.finalize().iter())
|
|
};
|
|
(apikey, apihash)
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub enum LoginError {
|
|
UnknownClientIP,
|
|
WrongClientIP,
|
|
WrongKey,
|
|
#[allow(dead_code)]
|
|
ConfigError(String),
|
|
}
|
|
|
|
/// Extra app config data
|
|
#[derive(rocket::serde::Deserialize)]
|
|
#[serde(crate = "rocket::serde")]
|
|
pub struct MinccinoConfig {
|
|
api_hash: String,
|
|
}
|
|
|
|
/// Confirms a vaid API key is being used, and blocks non-localhost requests
|
|
pub struct APIUser {
|
|
_priv: (),
|
|
}
|
|
|
|
#[rocket::async_trait]
|
|
impl<'r> FromRequest<'r> for APIUser {
|
|
type Error = LoginError;
|
|
|
|
async fn from_request(request: &'r Request<'_>) -> Outcome<APIUser, LoginError> {
|
|
// get the request ip, and check if it's localhost
|
|
let client_ip = match request.client_ip() {
|
|
Some(ip) => ip,
|
|
_ => return Outcome::Failure((Status::Forbidden, LoginError::UnknownClientIP)),
|
|
};
|
|
|
|
if !client_ip.is_loopback() {
|
|
return Outcome::Failure((Status::Forbidden, LoginError::WrongClientIP));
|
|
}
|
|
|
|
// get the API key from X-API-Key
|
|
let key_header = request.headers().get("x-api-key").collect::<Vec<&str>>();
|
|
let apikey = match key_header.len() {
|
|
1 => key_header[0],
|
|
_ => return Outcome::Failure((Status::Unauthorized, LoginError::WrongKey)),
|
|
};
|
|
|
|
// get api key hash from config
|
|
#[cfg(test)]
|
|
let app_config: MinccinoConfig = {
|
|
warn!("/!\\ /!\\ FAKE API KEY IN USE /!\\ /!\\");
|
|
warn!("IF YOU SEE THIS IN PROD, SOMETHING IS VERY WRONG");
|
|
|
|
MinccinoConfig {
|
|
api_hash: String::from(SNAKEOIL_HASH),
|
|
}
|
|
};
|
|
#[cfg(not(test))]
|
|
let app_config: MinccinoConfig = match request.rocket().figment().extract_inner("minccino")
|
|
{
|
|
Ok(conf) => conf,
|
|
Err(err) => {
|
|
return Outcome::Failure((
|
|
Status::InternalServerError,
|
|
LoginError::ConfigError(format!("{:?}", err)),
|
|
))
|
|
}
|
|
};
|
|
|
|
let apihash = {
|
|
let mut hasher = Sha3_256::default();
|
|
hasher.update(apikey.as_bytes());
|
|
hex!(hasher.finalize().iter())
|
|
};
|
|
|
|
// make sure the hashed API key matches what we've got
|
|
if app_config.api_hash != apihash {
|
|
return Outcome::Failure((Status::Forbidden, LoginError::WrongKey));
|
|
}
|
|
|
|
// and we're good!
|
|
Outcome::Success(APIUser { _priv: () })
|
|
}
|
|
}
|