thoth-api 0.2.1

GraphQL API for bibliographic data
Documentation
use diesel::prelude::*;
use dotenv::dotenv;
use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, Validation};
use regex::Regex;
use std::env;
use std::time::SystemTime;
use std::time::UNIX_EPOCH;
use uuid::Uuid;

use crate::account::model::Account;
use crate::account::model::AccountData;
use crate::account::model::DecodedToken;
use crate::account::model::NewAccount;
use crate::account::model::Token;
use crate::account::util::make_hash;
use crate::account::util::make_salt;
use crate::db::PgPool;
use crate::errors::ThothError;

impl Account {
    pub fn issue_token(&self, pool: &PgPool) -> Result<String, ThothError> {
        const DEFAULT_TOKEN_VALIDITY: i64 = 24 * 60 * 60;
        let connection = pool.get().unwrap();
        dotenv().ok();
        let secret_str = env::var("SECRET_KEY").expect("SECRET_KEY must be set");
        let secret: &[u8] = secret_str.as_bytes();
        let now = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .map_err(|_| ThothError::InternalError("Unable to set token iat".into()))?;
        let claim = Token {
            sub: self.email.clone(),
            exp: now.as_secs() as i64 + DEFAULT_TOKEN_VALIDITY,
            iat: now.as_secs() as i64,
            jti: Uuid::new_v4().to_string(),
        };
        let token = encode(
            &Header::default(),
            &claim,
            &EncodingKey::from_secret(secret),
        )
        .map_err(|_| ThothError::InternalError("Unable to create token".into()));

        use crate::schema::account::dsl;
        let updated_account = diesel::update(dsl::account.find(self.account_id))
            .set(dsl::token.eq(token.unwrap()))
            .get_result::<Account>(&connection)
            .expect("Unable to set token");
        Ok(updated_account.token.unwrap())
    }
}

impl From<AccountData> for NewAccount {
    fn from(account_data: AccountData) -> Self {
        let AccountData {
            name,
            surname,
            email,
            password,
            is_admin,
            is_bot,
            ..
        } = account_data;

        let salt = make_salt();
        let hash = make_hash(&password, &salt).to_vec();
        Self {
            name,
            surname,
            email,
            hash,
            salt,
            is_admin,
            is_bot,
        }
    }
}

impl Token {
    pub fn verify(token: &str) -> Result<Token, ThothError> {
        dotenv().ok();
        let secret_str = env::var("SECRET_KEY").expect("SECRET_KEY must be set");
        let secret: &[u8] = secret_str.as_bytes();

        let data = decode::<Token>(
            token,
            &DecodingKey::from_secret(secret),
            &Validation::default(),
        )
        .map_err(|_| ThothError::InvalidToken)?;
        Ok(data.claims)
    }
}

lazy_static::lazy_static! {
    static ref BEARER_REGEXP : Regex = Regex::new(r"^Bearer\s(.*)$").expect("Bearer regexp failed!");
}

impl actix_web::FromRequest for DecodedToken {
    type Error = actix_web::Error;
    type Future = futures::future::Ready<Result<Self, Self::Error>>;
    type Config = ();

    fn from_request(req: &actix_web::HttpRequest, _: &mut actix_web::dev::Payload) -> Self::Future {
        let token = req
            .headers()
            .get(actix_web::http::header::AUTHORIZATION)
            .and_then(|v| v.to_str().ok())
            .and_then(|authorization| {
                BEARER_REGEXP
                    .captures(authorization)
                    .and_then(|captures| captures.get(1))
            })
            .map(|v| v.as_str());

        futures::future::ready(Ok(match token {
            None => DecodedToken { jwt: None },
            Some(token) => match Token::verify(token) {
                Ok(decoded) => DecodedToken { jwt: Some(decoded) },
                Err(_) => DecodedToken { jwt: None },
            },
        }))
    }
}