zero4rs 2.0.0

zero4rs is a powerful, pragmatic, and extremely fast web framework for Rust
Documentation
use lazy_static::lazy_static;

use std::future::{ready, Ready};
use std::str::FromStr;

use actix_web::FromRequest;
// use actix_web::Error as ActixWebError;
use actix_web::dev::Payload;
use actix_web::http;
use actix_web::HttpRequest;

use crate::commons::{aes_256_decrypt, aes_256_encrypt};
use crate::core::error2::Error as _Error;
use crate::core::error2::Result;

lazy_static! {
    #[rustfmt::skip]
    static ref JWT_ENCODING_KEY: String = crate::commons::read_env("JWT_ENCODING_KEY", "super-long-and-jwt-secret-random-key");
    static ref JWT_SECRET_KEY: String = crate::commons::read_env("JWT_SECRET_KEY", "[},3]peS-ZF1&)>J}0X&V/Vv3ffAVr@Z");
    // static ref JWT_SECRET_IV: String = crate::commons::read_env("JWT_SECRET_IV", "hX%Jv1Gg:=ah%ul;");
}

// https://jwt.io/
#[rustfmt::skip]
#[derive(serde::Serialize, serde::Deserialize, Debug)]
pub struct Claims {
    // pub id: String,
    pub iss: String,    // Issuer: 表示 JWT 的签发者(issuer),即创建和签署 JWT 的实体(通常是服务端)。该字段的值是一个字符串,表示 JWT 的签发者
    pub exp: i64,       // Expiration Time: 表示 JWT 的过期时间,以 UNIX 时间戳表示,即从 1970 年 1 月 1 日午夜(UTC)开始计算的
    // user_id
    pub sub: String,    // Subject: 表示令牌所代表的主题或主体。通常,这是令牌所描述的用户或实体的唯一标识符。
    // aud(如果是用户指的是用户名, 如果是应用指的是分发给应用的id), user_name
    pub aud: String,    // Audience: 表示令牌的受众。这是一个字符串或字符串数组,指示令牌的预期接收方。通常,这用于确保令牌只能被特定的应用程序或服务接收。(app_id)
    pub nbf: i64,       // Not Before: 表示令牌的生效时间。这是一个 UNIX 时间戳,指示令牌在此时间之前不可用。通常,这用于预防令牌在某些时间之前被使用。
    pub iat: i64,       // Issued At: 表示令牌的签发时间。这是一个 UNIX 时间戳,指示令牌何时发放。
    pub jti: String,    // JWT ID: 表示令牌的唯一标识符。这个标识符通常用于防止重放攻击,确保每个令牌只能使用一次。
}

#[derive(serde::Serialize, serde::Deserialize)]
pub struct JwtToken {
    pub jti: String,
}

impl Claims {
    pub fn is_expired(&self) -> bool {
        let now = crate::commons::timestamp_millis() / 1000;
        if now <= self.exp && now >= self.nbf {
            // 有效
            false
        } else {
            true
        }
    }
}

impl JwtToken {
    pub fn generate_token(user_id: &str, user_name: &str) -> Result<String> {
        let now = chrono::Utc::now();
        let exp: i64 = (now + chrono::Duration::try_days(7).unwrap()).timestamp();
        let app_name = crate::commons::read_env("APP_NAME", "APP_NAME_UNSET");

        let claims: Claims = Claims {
            iss: app_name,
            exp,
            sub: user_id.to_owned(),
            aud: user_name.to_owned(),
            iat: now.timestamp(),
            nbf: now.timestamp() - 5,
            jti: uuid::Uuid::now_v7().to_string(),
        };

        let token_string = jsonwebtoken::encode(
            &jsonwebtoken::Header::new(jsonwebtoken::Algorithm::HS256),
            &claims,
            &jsonwebtoken::EncodingKey::from_secret(JWT_ENCODING_KEY.as_bytes()),
        )
        .map_err(|e| anyhow::anyhow!(e))?;

        aes_256_encrypt(&JWT_SECRET_KEY, &token_string).map_err(_Error::run_time)
    }

    pub fn verify_token(jwt_token: &str) -> Result<Claims> {
        let jwt_token =
            aes_256_decrypt(&JWT_SECRET_KEY, jwt_token).map_err(|e| anyhow::anyhow!(e))?;

        let mut validation = jsonwebtoken::Validation::new(jsonwebtoken::Algorithm::HS256);

        validation.validate_aud = false;

        let token = jsonwebtoken::decode::<Claims>(
            &jwt_token,
            &jsonwebtoken::DecodingKey::from_secret(JWT_ENCODING_KEY.as_bytes()),
            &validation,
        )
        .map_err(|e| {
            log::info!("verify_token: error={:?}", e);
            anyhow::anyhow!(e)
        })?;

        Ok(token.claims)
    }

    pub fn get_jwt_token(req: &actix_web::dev::ServiceRequest) -> Option<String> {
        let authorization = req
            .headers()
            .get("Authorization")
            .map(|val| val.to_str().unwrap_or_default());

        if let Some(s) = authorization {
            if s.starts_with("Bearer ") {
                match s.find(' ') {
                    None => None,
                    Some(index) => String::from_str(&s[index + 1..]).ok(),
                }
            } else {
                None
            }
        } else {
            None
        }
    }

    pub fn get_claims(req: &actix_web::dev::ServiceRequest) -> (Option<Claims>, usize, bool) {
        if let Some(jwt_token) = Self::get_jwt_token(req) {
            match JwtToken::verify_token(&jwt_token) {
                Ok(claims) => (Some(claims), 200, true),
                Err(e) => {
                    if e.to_string().contains("invalid utf-8 sequence")
                        || e.to_string().contains("missing field")
                    {
                        log::info!("verify_token-bad: error={}", e);
                        return (None, 400, true); // token数据非法
                    }

                    log::info!("verify_token-expired: error={}", e);
                    (None, 401, true)
                }
            }
        } else {
            // 没有token
            (None, 200, false)
        }
    }
}

impl FromRequest for JwtToken {
    type Error = _Error;
    type Future = Ready<core::result::Result<Self, Self::Error>>;

    fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
        // 1. get auth token from the authorization header
        let auth_token = if let Some(s) = req.headers().get(http::header::AUTHORIZATION) {
            match s.to_str() {
                Ok(o) => o.to_string(),
                Err(_) => {
                    return ready(Err(_Error::InvalidCredentials(
                        "Access Denied.".to_string(),
                    )));
                }
            }
        } else {
            return ready(Err(_Error::InvalidCredentials(
                "Access Denied!".to_string(),
            )));
        };

        let key = "aa"; // 秘钥匙(和加密的时候一样的key )

        // 2. decode token with secret
        let result = jsonwebtoken::decode::<Claims>(
            &auth_token,
            &jsonwebtoken::DecodingKey::from_secret(key.as_bytes()),
            &jsonwebtoken::Validation::new(jsonwebtoken::Algorithm::HS256),
        );

        // 3. return JwtToken
        match result {
            Ok(token) => {
                log::info!("jwt-header: debug={:?}", token.header);

                ready(Ok(JwtToken {
                    jti: token.claims.jti,
                }))
            }
            Err(_) => ready(Err(_Error::InvalidCredentials(
                "Invalid auth token!".to_string(),
            ))),
        }
    }
}