use actix::Message; use model::{AccountId, Audience, Token}; use sqlx::PgPool; use crate::{db_async_handler, Result}; #[derive(Debug, Copy, Clone, serde::Serialize, thiserror::Error)] pub enum Error { #[error("Failed to save new token")] Create, #[error("Failed to find token by jti")] Jti, } /// Find token by JTI field /// /// # Examples /// /// ``` /// use actix::Addr; /// use database_manager::{Database, TokenByJti}; /// /// async fn find(db: Addr) { /// match db.send(TokenByJti { /// jti: uuid::Uuid::new_v4() /// }).await { /// Ok(Ok(token)) => { println!("{:?}", token); } /// Ok(Err(db_err)) => { println!("{:?}", db_err); } /// Err(actor_err) => { println!("{:?}", actor_err); } /// } /// } /// ``` #[derive(Message)] #[rtype(result = "Result")] pub struct TokenByJti { pub jti: uuid::Uuid, } db_async_handler!(TokenByJti, token_by_jti, Token); pub(crate) async fn token_by_jti(msg: TokenByJti, pool: PgPool) -> Result { sqlx::query_as(r#" SELECT id, customer_id, role, issuer, subject, audience, expiration_time, not_before_time, issued_at_time, jwt_id FROM tokens WHERE jwt_id = $1 AND expiration_time > now() "#) .bind(msg.jti) .fetch_one(&pool) .await .map_err(|e| { log::error!("{e:?}"); crate::Error::Token(Error::Jti) }) } #[derive(Message)] #[rtype(result = "Result")] pub struct CreateToken { pub customer_id: uuid::Uuid, pub role: model::Role, pub subject: AccountId, pub audience: Audience, } db_async_handler!(CreateToken, create_token, Token); pub(crate) async fn create_token(msg: CreateToken, pool: PgPool) -> Result { let CreateToken { customer_id, role, subject, audience, } = msg; sqlx::query_as(r#" INSERT INTO tokens (customer_id, role, subject, audience) VALUES ($1, $2, $3, $4) RETURNING id, customer_id, role, issuer, subject, audience, expiration_time, not_before_time, issued_at_time, jwt_id "#) .bind(customer_id) .bind(role) .bind(subject) .bind(audience) .fetch_one(&pool) .await .map_err(|e| { log::error!("{e:?}"); crate::Error::Token(Error::Create) }) } #[derive(Message)] #[rtype(result = "Result")] pub struct CreateExtendedToken { pub customer_id: uuid::Uuid, pub role: model::Role, pub subject: AccountId, pub audience: Audience, pub expiration_time: chrono::NaiveDateTime, } db_async_handler!(CreateExtendedToken, create_extended_token, Token); pub(crate) async fn create_extended_token(msg: CreateExtendedToken, pool: PgPool) -> Result { let CreateExtendedToken { customer_id, role, subject, audience, expiration_time, } = msg; sqlx::query_as(r#" INSERT INTO tokens (customer_id, role, subject, audience, expiration_time) VALUES ($1, $2, $3, $4, $5) RETURNING id, customer_id, role, issuer, subject, audience, expiration_time, not_before_time, issued_at_time, jwt_id "#) .bind(customer_id) .bind(role) .bind(subject) .bind(audience) .bind(expiration_time) .fetch_one(&pool) .await .map_err(|e| { log::error!("{e:?}"); crate::Error::Token(Error::Create) }) } #[cfg(test)] mod tests { use actix::Addr; use config::UpdateConfig; use fake::Fake; use model::*; use uuid::Uuid; use crate::*; pub struct NoOpts; impl UpdateConfig for NoOpts {} async fn test_create_account(db: Addr) -> FullAccount { use fake::faker::internet::en; let login: String = en::Username().fake(); let email: String = en::FreeEmail().fake(); let hash: String = en::Password(10..20).fake(); db.send(CreateAccount { email: Email::new(email), login: Login::new(login), pass_hash: PassHash::new(hash), role: Role::Admin, }) .await .unwrap() .unwrap() } async fn test_create_token_extended( db: Addr, customer_id: Option, role: Option, subject: Option, audience: Option, expiration_time: Option, ) -> Token { let customer_id = customer_id.unwrap_or_else(|| Uuid::new_v4()); let role = role.unwrap_or_else(|| Role::Admin); let subject = match subject { Some(id) => id, _ => test_create_account(db.clone()).await.id, }; let audience = audience.unwrap_or_else(|| Audience::Web); let expiration_time = expiration_time .unwrap_or_else(|| (chrono::Utc::now() + chrono::Duration::days(60)).naive_utc()); db.send(CreateExtendedToken { customer_id, role, subject, audience, expiration_time, }) .await .unwrap() .unwrap() } #[actix::test] async fn create_token() { let config = config::default_load(&mut NoOpts); config .lock() .database_mut() .set_url("postgres://postgres@localhost/bazzar_test"); let db = Database::build(config).await.start(); db.send(CreateToken { customer_id: Uuid::new_v4(), role: Role::Admin, subject: test_create_account(db.clone()).await.id, audience: Audience::Web, }) .await .unwrap() .unwrap(); } #[actix::test] async fn create_extended_token() { let config = config::default_load(&mut NoOpts); config .lock() .database_mut() .set_url("postgres://postgres@localhost/bazzar_test"); let db = Database::build(config).await.start(); test_create_account(db).await; } }