Тесты JWT

Имплементация PartialEq для utils::jwt::VerifyError

Замена устаревшего changeset_options на diesel

Удалена проверка на ошибку создания токена, так как вероятность её появления близка к нулю
This commit is contained in:
2025-03-22 23:14:14 +04:00
parent ba86dfc3fe
commit 844c89a365
3 changed files with 88 additions and 26 deletions

View File

@@ -1,5 +1,4 @@
use diesel::prelude::*; use diesel::prelude::*;
use diesel::{AsExpression, FromSqlRow};
use serde::Serialize; use serde::Serialize;
#[derive(diesel_derive_enum::DbEnum, Serialize, Debug, Clone, Copy, PartialEq)] #[derive(diesel_derive_enum::DbEnum, Serialize, Debug, Clone, Copy, PartialEq)]
@@ -14,7 +13,7 @@ pub enum UserRole {
#[derive(Identifiable, AsChangeset, Queryable, Selectable, Serialize)] #[derive(Identifiable, AsChangeset, Queryable, Selectable, Serialize)]
#[diesel(table_name = crate::database::schema::users)] #[diesel(table_name = crate::database::schema::users)]
#[changeset_options(treat_none_as_null = "true")] #[diesel(treat_none_as_null = true)]
pub struct User { pub struct User {
pub id: String, pub id: String,
pub username: String, pub username: String,

View File

@@ -16,8 +16,7 @@ pub async fn sign_in(data: Json<SignInDto>, app_state: web::Data<AppState>) -> J
let mut lock = app_state.connection(); let mut lock = app_state.connection();
let conn = lock.deref_mut(); let conn = lock.deref_mut();
user.access_token = user.access_token = utility::jwt::encode(&user.id);
utility::jwt::encode(&user.id).expect("Failed to generate jet token");
user.save_changes::<User>(conn) user.save_changes::<User>(conn)
.expect("Failed to update user"); .expect("Failed to update user");

View File

@@ -1,9 +1,13 @@
use chrono::{DateTime, Duration, Utc}; use chrono::DateTime;
use chrono::Duration;
use chrono::TimeZone;
use chrono::Utc;
use hmac::{Hmac, Mac}; use hmac::{Hmac, Mac};
use jwt::{SignWithKey, Token, VerifyWithKey}; use jwt::{SignWithKey, Token, VerifyWithKey};
use sha2::Sha256; use sha2::Sha256;
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::env; use std::env;
use std::mem::discriminant;
use std::sync::LazyLock; use std::sync::LazyLock;
static JWT_SECRET: LazyLock<Hmac<Sha256>> = LazyLock::new(|| { static JWT_SECRET: LazyLock<Hmac<Sha256>> = LazyLock::new(|| {
@@ -16,9 +20,14 @@ static JWT_SECRET: LazyLock<Hmac<Sha256>> = LazyLock::new(|| {
pub enum VerifyError { pub enum VerifyError {
JwtError(jwt::Error), JwtError(jwt::Error),
InvalidSignature, InvalidSignature,
NoExpirationTag, InvalidToken,
Expired, Expired,
NoId, }
impl PartialEq for VerifyError {
fn eq(&self, other: &Self) -> bool {
discriminant(self) == discriminant(other)
}
} }
pub fn verify_and_decode(token: &String) -> Result<String, VerifyError> { pub fn verify_and_decode(token: &String) -> Result<String, VerifyError> {
@@ -27,31 +36,29 @@ pub fn verify_and_decode(token: &String) -> Result<String, VerifyError> {
let result: Result<BTreeMap<String, String>, jwt::Error> = token.verify_with_key(jwt); let result: Result<BTreeMap<String, String>, jwt::Error> = token.verify_with_key(jwt);
match result { match result {
Ok(claims) => match claims.get("exp") { Ok(claims) => {
None => Err(VerifyError::NoExpirationTag), let exp = claims.get("exp").unwrap();
Some(exp) => { let exp_date = DateTime::from_timestamp(exp.parse::<i64>().unwrap(), 0)
let exp_date = DateTime::from_timestamp(exp.parse::<i64>().unwrap(), 0) .expect("Failed to parse expiration time");
.expect("Failed to parse expiration time");
if Utc::now() > exp_date { if Utc::now() > exp_date {
return Err(VerifyError::Expired); return Err(VerifyError::Expired);
}
match claims.get("id").cloned() {
None => Err(VerifyError::NoId),
Some(id) => Ok(id),
}
} }
},
Ok(claims.get("id").cloned().unwrap())
}
Err(err) => Err(match err { Err(err) => Err(match err {
jwt::Error::InvalidSignature => VerifyError::InvalidSignature, jwt::Error::InvalidSignature => VerifyError::InvalidSignature,
jwt::Error::Format | jwt::Error::Base64(_) | jwt::Error::NoClaimsComponent => {
VerifyError::InvalidToken
}
_ => VerifyError::JwtError(err), _ => VerifyError::JwtError(err),
}), }),
} }
} }
pub fn encode(id: &String) -> Result<String, jwt::Error> { pub fn encode(id: &String) -> String {
let header = jwt::Header { let header = jwt::Header {
type_: Some(jwt::header::HeaderType::JsonWebToken), type_: Some(jwt::header::HeaderType::JsonWebToken),
..Default::default() ..Default::default()
@@ -69,8 +76,65 @@ pub fn encode(id: &String) -> Result<String, jwt::Error> {
claims.insert("iat", iat_str.as_str()); claims.insert("iat", iat_str.as_str());
claims.insert("exp", exp_str.as_str()); claims.insert("exp", exp_str.as_str());
match Token::new(header, claims).sign_with_key(&*JWT_SECRET) { Token::new(header, claims)
Ok(token) => Ok(token.as_str().to_string()), .sign_with_key(&*JWT_SECRET)
Err(err) => Err(err), .unwrap()
.as_str()
.to_string()
}
#[cfg(test)]
mod tests {
use super::*;
use dotenvy::dotenv;
#[test]
fn test_encode() {
dotenv().unwrap();
assert_eq!(encode(&"test".to_string()).is_empty(), false);
}
#[test]
fn test_decode_invalid_token() {
dotenv().unwrap();
let token = "".to_string();
let result = verify_and_decode(&token);
assert!(result.is_err());
assert_eq!(result.err().unwrap(), VerifyError::InvalidToken);
}
#[test]
fn test_decode_invalid_signature() {
dotenv().unwrap();
let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOiIxODY4ODEyOTI4IiwiaWF0IjoiMTc0MjY2ODkyOCIsImlkIjoiNjdkY2M5YTk1MDdiMDAwMDc3Mjc0NGEyIn0.DQYFYF-3DoJgCLOVdAWa47nUaCJAh16DXj-ChNSSmWz".to_string();
let result = verify_and_decode(&token);
assert!(result.is_err());
assert_eq!(result.err().unwrap(), VerifyError::InvalidToken);
}
#[test]
fn test_decode_expired() {
dotenv().unwrap();
let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOiIxNjE2NTI2Mzc2IiwiaWF0IjoiMTQ5MDM4MjM3NiIsImlkIjoiNjdkY2M5YTk1MDdiMDAwMDc3Mjc0NGEyIn0.Qc2LbMJTvl2hWzDM2XyQv4m9lIqR84COAESQAieUxz8".to_string();
let result = verify_and_decode(&token);
assert!(result.is_err());
assert_eq!(result.err().unwrap(), VerifyError::Expired);
}
#[test]
fn test_decode_ok() {
dotenv().unwrap();
let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOiIxODY4ODEyOTI4IiwiaWF0IjoiMTc0MjY2ODkyOCIsImlkIjoiNjdkY2M5YTk1MDdiMDAwMDc3Mjc0NGEyIn0.DQYFYF-3DoJgCLOVdAWa47nUaCJAh16DXj-ChNSSmWw".to_string();
let result = verify_and_decode(&token);
assert!(result.is_ok());
} }
} }