minccino/src/yui/auth.rs
2022-09-23 22:39:17 -07:00

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: () })
}
}