use lazy_static::lazy_static;
use std::future::{ready, Ready};
use std::str::FromStr;
use actix_web::FromRequest;
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");
}
#[rustfmt::skip]
#[derive(serde::Serialize, serde::Deserialize, Debug)]
pub struct Claims {
pub iss: String, pub exp: i64, pub sub: String, pub aud: String, pub nbf: i64, pub iat: i64, pub jti: String, }
#[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); }
log::info!("verify_token-expired: error={}", e);
(None, 401, true)
}
}
} else {
(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 {
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";
let result = jsonwebtoken::decode::<Claims>(
&auth_token,
&jsonwebtoken::DecodingKey::from_secret(key.as_bytes()),
&jsonwebtoken::Validation::new(jsonwebtoken::Algorithm::HS256),
);
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(),
))),
}
}
}