use crate::{ args, db, models::*, monero, reqres, user, utils, }; use clap::Parser; use log::{ debug, error, info, }; use rocket::{ http::Status, outcome::Outcome, request, request::FromRequest, Request, }; use hmac::{ Hmac, Mac, }; use jwt::*; use sha2::Sha384; use std::collections::BTreeMap; /// Create authorization data to sign and expiration pub fn create(address: &String) -> Authorization { info!("creating auth"); let aid: String = format!("auth{}", utils::generate_rnd()); let rnd: String = utils::generate_rnd(); let created: i64 = chrono::offset::Utc::now().timestamp(); let token: String = create_token(String::from(address), created); let new_auth = Authorization { aid, created, uid: utils::empty_string(), rnd, token, xmr_address: String::from(address), }; let s = db::Interface::open(); debug!("insert auth: {:?}", &new_auth); let k = &new_auth.aid; db::Interface::write(&s.env, &s.handle, k, &Authorization::to_db(&new_auth)); new_auth } /// Authorization lookup for recurring requests pub fn find(aid: &String) -> Authorization { info!("searching for auth: {}", aid); let s = db::Interface::open(); let r = db::Interface::read(&s.env, &s.handle, &String::from(aid)); debug!("auth read: {}", r); if r == utils::empty_string() { return Default::default(); } Authorization::from_db(String::from(aid), r) } /// Update new authorization creation time fn update_expiration(f_auth: Authorization, address: &String) -> Authorization { info!("modify auth expiration"); let data = utils::generate_rnd(); let time: i64 = chrono::offset::Utc::now().timestamp(); // update time, token and data to sign let u_auth = Authorization::update_expiration( f_auth, time, data, create_token(String::from(address), time), ); let s = db::Interface::open(); db::Interface::delete(&s.env, &s.handle, &u_auth.aid); db::Interface::write( &s.env, &s.handle, &u_auth.aid, &Authorization::to_db(&u_auth), ); return u_auth; } /// Performs the signature verfication against stored auth pub async fn verify_login(aid: String, uid: String, signature: String) -> Authorization { let m_address: reqres::XmrRpcAddressResponse = monero::get_address().await; let address = m_address.result.address; let f_auth: Authorization = find(&aid); if f_auth.xmr_address == utils::empty_string() { error!("auth not found"); return create(&address); } let data: String = String::from(&f_auth.rnd); let sig_address: String = monero::verify_signature(String::from(&address), data, String::from(&signature)).await; if sig_address == utils::ApplicationErrors::LoginError.value() { error!("signature validation failed"); return f_auth; } let f_user: User = user::find(&uid); if f_user.xmr_address == utils::empty_string() { info!("creating new user"); let u: User = user::create(&address); // update auth with uid let u_auth = Authorization::update_uid(f_auth, String::from(&u.uid)); let s = db::Interface::open(); db::Interface::delete(&s.env, &s.handle, &u_auth.aid); db::Interface::write( &s.env, &s.handle, &u_auth.aid, &Authorization::to_db(&u_auth), ); return u_auth; } else if f_user.xmr_address != utils::empty_string() { info!("returning user"); let m_access = verify_access(&address, &signature).await; if !m_access { return Default::default(); } return f_auth; } else { error!("error creating user"); return Default::default(); } } /// Called during auth flow to update data to sign and expiration pub async fn verify_access(address: &String, signature: &String) -> bool { // look up auth for address let f_auth: Authorization = find(address); if f_auth.xmr_address != utils::empty_string() { // check expiration, generate new data to sign if necessary let now: i64 = chrono::offset::Utc::now().timestamp(); let expiration = get_auth_expiration(); if now > f_auth.created + expiration { update_expiration(f_auth, address); return false; } } // verify signature on the data if not expired let data = f_auth.rnd; let sig_address: String = monero::verify_signature(String::from(address), data, String::from(signature)).await; if sig_address == utils::ApplicationErrors::LoginError.value() { debug!("signing failed"); return false; } info!("auth verified"); return true; } /// get the auth expiration command line configuration fn get_auth_expiration() -> i64 { let args = args::Args::parse(); args.token_timeout * 60 } fn create_token(address: String, created: i64) -> String { let jwt_secret_key = utils::get_jwt_secret_key(); let key: Hmac = Hmac::new_from_slice(&jwt_secret_key.as_bytes()).expect("hash"); let header = Header { algorithm: AlgorithmType::Hs384, ..Default::default() }; let mut claims = BTreeMap::new(); let expiration = get_auth_expiration() * created; claims.insert("address", address); claims.insert("expiration", expiration.to_string()); let token = Token::new(header, claims).sign_with_key(&key); String::from(token.expect("expected token").as_str()) } /// This token is used for internal micro server authentication #[derive(Debug)] pub struct BearerToken(String); #[derive(Debug)] pub enum BearerTokenError { Expired, Missing, Invalid, } #[rocket::async_trait] impl<'r> FromRequest<'r> for BearerToken { type Error = BearerTokenError; async fn from_request(request: &'r Request<'_>) -> request::Outcome { let env = utils::get_release_env(); let dev = utils::ReleaseEnvironment::Development; if env == dev { return Outcome::Success(BearerToken(utils::empty_string())); } let token = request.headers().get_one("token"); let m_address: reqres::XmrRpcAddressResponse = monero::get_address().await; let address = m_address.result.address; debug!("{}", address); match token { Some(token) => { // check validity let jwt_secret_key = utils::get_jwt_secret_key(); let key: Hmac = Hmac::new_from_slice(&jwt_secret_key.as_bytes()).expect(""); let jwt: Result< Token, _>, jwt::Error, > = token.verify_with_key(&key); return match jwt { Ok(j) => { let claims = j.claims(); debug!("claim address: {}", claims["address"]); // verify address if claims["address"] != address { return Outcome::Failure(( Status::Unauthorized, BearerTokenError::Invalid, )); } // verify expiration let now: i64 = chrono::offset::Utc::now().timestamp(); let expire = match claims["expiration"].parse::() { Ok(n) => n, Err(_) => 0, }; if now > expire { return Outcome::Failure(( Status::Unauthorized, BearerTokenError::Expired, )); } Outcome::Success(BearerToken(String::from(token))) } Err(_) => Outcome::Failure((Status::Unauthorized, BearerTokenError::Invalid)), }; } None => Outcome::Failure((Status::Unauthorized, BearerTokenError::Missing)), } } }