133 lines
3.9 KiB
Rust
133 lines
3.9 KiB
Rust
use crate::config::Config;
|
|
use jwt_simple::common::VerificationOptions;
|
|
use jwt_simple::prelude::*;
|
|
use serde::{Deserialize, Serialize};
|
|
use std::collections::HashSet;
|
|
use tokio::fs;
|
|
|
|
use crate::message::JWTMessage;
|
|
use crate::stores::Credentials;
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
struct JWTCustomClaims {
|
|
email: String,
|
|
}
|
|
|
|
pub struct JWTSigner {
|
|
private_key: String,
|
|
public_key: String,
|
|
issuer: String,
|
|
exp_time: u64,
|
|
}
|
|
|
|
impl JWTSigner {
|
|
// NOTE: could be included in a Trait: `TryFrom` but difficult to handle with async
|
|
pub async fn new(config: Config) -> Result<Self, String> {
|
|
let mut jwt_signer = JWTSigner {
|
|
private_key: "".to_string(),
|
|
public_key: "".to_string(),
|
|
issuer: config.jwt_issuer,
|
|
exp_time: config.jwt_exp_time,
|
|
};
|
|
|
|
match fs::read_to_string(config.jwt_priv_key).await {
|
|
Ok(c) => {
|
|
jwt_signer.private_key = c;
|
|
}
|
|
Err(e) => {
|
|
return Err(format!("unable to read the private key details={}", e));
|
|
}
|
|
}
|
|
|
|
match fs::read_to_string(config.jwt_pub_key).await {
|
|
Ok(c) => {
|
|
jwt_signer.public_key = c;
|
|
}
|
|
Err(e) => {
|
|
return Err(format!("unable to read the public key details={}", e));
|
|
}
|
|
}
|
|
|
|
Ok(jwt_signer)
|
|
}
|
|
|
|
fn get_verification_options(&self) -> VerificationOptions {
|
|
let mut verification_options = VerificationOptions::default();
|
|
|
|
let mut issuers: HashSet<String> = HashSet::new();
|
|
issuers.insert(self.issuer.clone());
|
|
verification_options.allowed_issuers = Some(issuers);
|
|
|
|
verification_options
|
|
}
|
|
|
|
/// sign builds and signs the token
|
|
pub fn sign(&self, credentials: Credentials) -> Result<String, String> {
|
|
let jwt_key = {
|
|
match RS384KeyPair::from_pem(&self.private_key) {
|
|
Ok(k) => k,
|
|
Err(e) => {
|
|
return Err(format!("unable to load the private key details={}", e));
|
|
}
|
|
}
|
|
};
|
|
let mut claims = Claims::with_custom_claims(
|
|
JWTCustomClaims {
|
|
email: credentials.get_email(),
|
|
},
|
|
Duration::from_hours(self.exp_time),
|
|
);
|
|
claims.issuer = Some(self.issuer.clone());
|
|
|
|
match jwt_key.sign(claims) {
|
|
Ok(token) => {
|
|
// TODO: need to generate the refresh token
|
|
return Ok(serde_json::to_string(&JWTMessage::with_access(token)).unwrap());
|
|
}
|
|
Err(e) => {
|
|
return Err(format!("unable to sign the token details={}", e));
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn validate(&self, token: &str) -> Result<(), String> {
|
|
let verification_options = self.get_verification_options();
|
|
match RS384PublicKey::from_pem(&self.public_key) {
|
|
Ok(key) => {
|
|
if let Err(e) =
|
|
key.verify_token::<NoCustomClaims>(token, Some(verification_options))
|
|
{
|
|
return Err(format!("token validation failed details={}", e));
|
|
}
|
|
Ok(())
|
|
}
|
|
Err(e) => Err(format!(
|
|
"token validation failed, can't read the public key details={}",
|
|
e
|
|
)),
|
|
}
|
|
}
|
|
|
|
pub fn get_public_key(&self) -> String {
|
|
self.public_key.clone()
|
|
}
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_signer() {
|
|
use configparser::ini::Ini;
|
|
use std::env;
|
|
|
|
let root_path = env::var("CARGO_MANIFEST_DIR").unwrap();
|
|
|
|
let config_path = format!("{}/{}/{}/{}", root_path, "tests", "data", "config.ini");
|
|
let mut config = Ini::new();
|
|
let _r = config.load(config_path);
|
|
|
|
let router_config = Config::try_from(config);
|
|
assert!(router_config.is_ok());
|
|
|
|
let jwt_signer = JWTSigner::new(router_config.unwrap());
|
|
assert!(jwt_signer.await.is_ok());
|
|
}
|