Validate token

This commit is contained in:
eraden 2022-04-19 08:04:40 +02:00
parent 2c0104ec2d
commit 1667270d73
2 changed files with 58 additions and 55 deletions

View File

@ -1,8 +1,12 @@
use std::collections::BTreeMap;
use std::str::FromStr; use std::str::FromStr;
use std::sync::Arc; use std::sync::Arc;
use actix::{Addr, Message}; use actix::{Addr, Message};
use chrono::prelude::*; use chrono::prelude::*;
use hmac::digest::KeyInit;
use hmac::Hmac;
use sha2::Sha256;
use crate::database::{Database, TokenByJti}; use crate::database::{Database, TokenByJti};
use crate::model::{AccountId, Audience, Token, TokenString}; use crate::model::{AccountId, Audience, Token, TokenString};
@ -73,7 +77,7 @@ pub struct CreateToken {
token_async_handler!(CreateToken, create_token, (Token, TokenString)); token_async_handler!(CreateToken, create_token, (Token, TokenString));
async fn create_token( pub(crate) async fn create_token(
msg: CreateToken, msg: CreateToken,
db: Addr<Database>, db: Addr<Database>,
secret: Arc<String>, secret: Arc<String>,
@ -107,19 +111,9 @@ async fn create_token(
}; };
let token_string = { let token_string = {
use std::collections::BTreeMap;
use hmac::{Hmac, Mac};
use jwt::SignWithKey; use jwt::SignWithKey;
use sha2::Sha256;
let key: Hmac<Sha256> = match Hmac::new_from_slice(secret.as_bytes()) { let key: Hmac<Sha256> = build_key(secret)?;
Ok(key) => key,
Err(e) => {
log::error!("{e:?}");
return Err(Error::SaveInternal);
}
};
let mut claims = BTreeMap::new(); let mut claims = BTreeMap::new();
// cti (customer id): Customer uuid identifier used by payment service // cti (customer id): Customer uuid identifier used by payment service
@ -168,21 +162,11 @@ pub(crate) async fn validate(
db: Addr<Database>, db: Addr<Database>,
secret: Arc<String>, secret: Arc<String>,
) -> Result<(Token, bool)> { ) -> Result<(Token, bool)> {
use std::collections::BTreeMap;
use hmac::{Hmac, Mac};
use jwt::VerifyWithKey; use jwt::VerifyWithKey;
use sha2::Sha256;
log::info!("Validating token {:?}", msg.token); log::info!("Validating token {:?}", msg.token);
let key: Hmac<Sha256> = match Hmac::new_from_slice(secret.as_bytes()) { let key: Hmac<Sha256> = build_key(secret)?;
Ok(key) => key,
Err(e) => {
log::error!("{e:?}");
return Err(Error::ValidateInternal);
}
};
let claims: BTreeMap<String, String> = match msg.token.verify_with_key(&key) { let claims: BTreeMap<String, String> = match msg.token.verify_with_key(&key) {
Ok(claims) => claims, Ok(claims) => claims,
_ => return Err(Error::Validate), _ => return Err(Error::Validate),
@ -209,59 +193,72 @@ pub(crate) async fn validate(
} }
}; };
match (claims.get("cti"), &token.customer_id) { if !validate_pair(&claims, "cti", token.customer_id, validate_uuid) {
(Some(cti), id) => { return Ok((token, false));
if !uuid::Uuid::from_str(cti)
.map(|u| u == *id)
.unwrap_or_default()
{
return Ok((token, false));
}
}
_ => return Ok((token, false)),
} }
// if !validate_pair(&claims, "arl", token.role, |left, right| right == left) {
// return Ok((token, false));
// }
match (claims.get("arl"), &token.role) { match (claims.get("arl"), &token.role) {
(Some(arl), role) if arl == role.as_str() => {} (Some(arl), role) if role == arl.as_str() => {}
_ => return Ok((token, false)), _ => return Ok((token, false)),
} }
match (claims.get("iss"), &token.issuer) { match (claims.get("iss"), &token.issuer) {
(Some(iss), issuer) if iss == issuer => {} (Some(iss), issuer) if iss == issuer => {}
_ => return Ok((token, false)), _ => return Ok((token, false)),
} }
match (claims.get("sub"), &token.subject) { if !validate_pair(&claims, "sub", token.subject, validate_num) {
(Some(sub), subject) => { return Ok((token, false));
if !sub
.parse::<i32>()
.map(|n| n == *subject)
.unwrap_or_default()
{
return Ok((token, false));
}
}
_ => return Ok((token, false)),
} }
match (claims.get("aud"), &token.audience) { match (claims.get("aud"), &token.audience) {
(Some(aud), audience) if aud == audience.as_str() => {} (Some(aud), audience) if aud == audience.as_str() => {}
_ => return Ok((token, false)), _ => return Ok((token, false)),
} }
match (claims.get("exp"), &token.expiration_time) {
(Some(left), right) if validate_time(left, right) => {} if !validate_pair(&claims, "exp", &token.expiration_time, validate_time) {
_ => return Ok((token, false)), return Ok((token, false));
} }
match (claims.get("nbt"), &token.not_before_time) { if !validate_pair(&claims, "nbt", &token.not_before_time, validate_time) {
(Some(left), right) if validate_time(left, right) => {} return Ok((token, false));
_ => return Ok((token, false)),
} }
match (claims.get("iat"), &token.issued_at_time) { if !validate_pair(&claims, "iat", &token.issued_at_time, validate_time) {
(Some(left), right) if validate_time(left, right) => {} return Ok((token, false));
_ => return Ok((token, false)),
} }
Ok((token, true)) Ok((token, true))
} }
fn build_key(secret: Arc<String>) -> Result<Hmac<Sha256>> {
match Hmac::new_from_slice(secret.as_bytes()) {
Ok(key) => Ok(key),
Err(e) => {
log::error!("{e:?}");
Err(Error::ValidateInternal)
}
}
}
fn validate_pair<F, V>(claims: &BTreeMap<String, String>, key: &str, v: V, cmp: F) -> bool
where
F: FnOnce(&str, V) -> bool,
V: PartialEq,
{
claims.get(key).map(|s| cmp(s, v)).unwrap_or_default()
}
fn validate_time(left: &str, right: &NaiveDateTime) -> bool { fn validate_time(left: &str, right: &NaiveDateTime) -> bool {
chrono::DateTime::parse_from_str(left, "%+") chrono::DateTime::parse_from_str(left, "%+")
.map(|t| t.naive_utc() == *right) .map(|t| t.naive_utc() == *right)
.unwrap_or_default() .unwrap_or_default()
} }
fn validate_num(left: &str, right: i32) -> bool {
left.parse::<i32>().map(|n| n == right).unwrap_or_default()
}
fn validate_uuid(left: &str, right: uuid::Uuid) -> bool {
uuid::Uuid::from_str(left)
.map(|u| u == right)
.unwrap_or_default()
}

View File

@ -31,7 +31,7 @@ pub enum OrderStatus {
Refunded, Refunded,
} }
#[derive(sqlx::Type, Copy, Clone, Debug, Display, Deserialize, Serialize)] #[derive(sqlx::Type, Copy, Clone, Debug, Display, Deserialize, Serialize, PartialEq)]
#[sqlx(rename_all = "snake_case")] #[sqlx(rename_all = "snake_case")]
pub enum Role { pub enum Role {
#[display(fmt = "Adminitrator")] #[display(fmt = "Adminitrator")]
@ -40,6 +40,12 @@ pub enum Role {
User, User,
} }
impl PartialEq<str> for Role {
fn eq(&self, other: &str) -> bool {
self.as_str() == other
}
}
impl Role { impl Role {
pub fn as_str(&self) -> &str { pub fn as_str(&self) -> &str {
match self { match self {