bazzar/actors/database_manager/src/tokens.rs

277 lines
6.9 KiB
Rust
Raw Normal View History

2022-04-18 22:07:52 +02:00
use actix::Message;
2022-05-06 11:47:18 +02:00
use model::{AccountId, Audience, Token};
2022-04-18 22:07:52 +02:00
2022-05-06 16:02:38 +02:00
use crate::{db_async_handler, Result};
2022-04-18 22:07:52 +02:00
2022-06-04 16:55:29 +02:00
#[derive(Debug, Copy, Clone, PartialEq, serde::Serialize, thiserror::Error)]
2022-04-18 22:07:52 +02:00
pub enum Error {
#[error("Failed to save new token")]
Create,
#[error("Failed to find token by jti")]
Jti,
}
2022-06-04 16:05:18 +02:00
/// Find token by JTI field
///
/// # Examples
///
/// ```
/// use actix::Addr;
/// use database_manager::{Database, TokenByJti};
///
/// async fn find(db: Addr<Database>) {
/// 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); }
/// }
/// }
/// ```
2022-04-18 22:07:52 +02:00
#[derive(Message)]
#[rtype(result = "Result<Token>")]
pub struct TokenByJti {
2022-04-19 16:49:30 +02:00
pub jti: uuid::Uuid,
2022-04-18 22:07:52 +02:00
}
2022-06-05 21:03:22 +02:00
db_async_handler!(TokenByJti, token_by_jti, Token, inner_token_by_jti);
2022-04-18 22:07:52 +02:00
2022-06-05 21:03:22 +02:00
pub(crate) async fn token_by_jti(
msg: TokenByJti,
t: &mut sqlx::Transaction<'_, sqlx::Postgres>,
) -> Result<Token> {
2022-04-18 22:07:52 +02:00
sqlx::query_as(r#"
SELECT id, customer_id, role, issuer, subject, audience, expiration_time, not_before_time, issued_at_time, jwt_id
FROM tokens
2022-05-10 16:20:37 +02:00
WHERE jwt_id = $1 AND expiration_time > now()
2022-04-18 22:07:52 +02:00
"#)
.bind(msg.jti)
2022-06-05 21:03:22 +02:00
.fetch_one(t)
2022-04-18 22:07:52 +02:00
.await
.map_err(|e| {
tracing::error!("{e:?}");
2022-05-06 11:47:18 +02:00
crate::Error::Token(Error::Jti)
2022-04-18 22:07:52 +02:00
})
}
#[derive(Message)]
#[rtype(result = "Result<Token>")]
pub struct CreateToken {
pub customer_id: uuid::Uuid,
2022-05-06 11:47:18 +02:00
pub role: model::Role,
2022-04-18 22:07:52 +02:00
pub subject: AccountId,
pub audience: Audience,
}
2022-06-05 21:03:22 +02:00
db_async_handler!(CreateToken, create_token, Token, inner_create_token);
2022-04-18 22:07:52 +02:00
2022-06-05 21:03:22 +02:00
pub(crate) async fn create_token(
msg: CreateToken,
t: &mut sqlx::Transaction<'_, sqlx::Postgres>,
) -> Result<Token> {
2022-04-18 22:07:52 +02:00
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)
2022-06-05 21:03:22 +02:00
.fetch_one(t)
2022-04-18 22:07:52 +02:00
.await
.map_err(|e| {
tracing::error!("{e:?}");
2022-05-06 11:47:18 +02:00
crate::Error::Token(Error::Create)
2022-04-18 22:07:52 +02:00
})
}
2022-05-10 16:20:37 +02:00
#[derive(Message)]
#[rtype(result = "Result<Token>")]
pub struct CreateExtendedToken {
pub customer_id: uuid::Uuid,
pub role: model::Role,
pub subject: AccountId,
pub audience: Audience,
pub expiration_time: chrono::NaiveDateTime,
}
2022-06-05 21:03:22 +02:00
db_async_handler!(
CreateExtendedToken,
create_extended_token,
Token,
inner_create_extended_token
);
pub(crate) async fn create_extended_token(
msg: CreateExtendedToken,
t: &mut sqlx::Transaction<'_, sqlx::Postgres>,
) -> Result<Token> {
2022-05-10 16:20:37 +02:00
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)
2022-06-05 21:03:22 +02:00
.fetch_one(t)
2022-05-10 16:20:37 +02:00
.await
.map_err(|e| {
tracing::error!("{e:?}");
2022-05-10 16:20:37 +02:00
crate::Error::Token(Error::Create)
})
}
2022-06-04 16:05:18 +02:00
#[cfg(test)]
mod tests {
2022-06-04 16:42:27 +02:00
use config::UpdateConfig;
use fake::Fake;
2022-06-04 16:05:18 +02:00
use model::*;
use uuid::Uuid;
use crate::*;
2022-06-04 16:42:27 +02:00
pub struct NoOpts;
impl UpdateConfig for NoOpts {}
2022-06-05 21:03:22 +02:00
async fn test_create_account(t: &mut sqlx::Transaction<'_, sqlx::Postgres>) -> FullAccount {
2022-06-04 16:05:18 +02:00
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();
2022-06-05 21:03:22 +02:00
crate::create_account(
CreateAccount {
email: Email::new(email),
login: Login::new(login),
pass_hash: PassHash::new(hash),
role: Role::Admin,
},
t,
)
2022-06-04 16:05:18 +02:00
.await
.unwrap()
}
2022-06-04 16:42:27 +02:00
async fn test_create_token_extended(
2022-06-05 21:03:22 +02:00
t: &mut sqlx::Transaction<'_, sqlx::Postgres>,
2022-06-04 16:42:27 +02:00
customer_id: Option<Uuid>,
2022-06-04 16:05:18 +02:00
role: Option<Role>,
subject: Option<AccountId>,
audience: Option<Audience>,
expiration_time: Option<chrono::NaiveDateTime>,
) -> Token {
2022-06-04 16:42:27 +02:00
let customer_id = customer_id.unwrap_or_else(|| Uuid::new_v4());
2022-06-04 16:05:18 +02:00
let role = role.unwrap_or_else(|| Role::Admin);
let subject = match subject {
Some(id) => id,
2022-06-05 21:03:22 +02:00
_ => test_create_account(t).await.id,
2022-06-04 16:05:18 +02:00
};
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());
2022-06-05 21:03:22 +02:00
super::create_extended_token(
CreateExtendedToken {
customer_id,
role,
subject,
audience,
expiration_time,
},
t,
)
2022-06-04 16:05:18 +02:00
.await
.unwrap()
}
#[actix::test]
2022-06-04 16:42:27 +02:00
async fn create_token() {
2022-06-05 21:03:22 +02:00
testx::db_t!(t);
super::create_token(
CreateToken {
customer_id: Uuid::new_v4(),
role: Role::Admin,
subject: test_create_account(&mut t).await.id,
audience: Audience::Web,
},
&mut t,
)
2022-06-04 16:05:18 +02:00
.await
.unwrap();
}
2022-06-04 16:42:27 +02:00
#[actix::test]
async fn create_extended_token() {
2022-06-05 21:03:22 +02:00
testx::db_t!(t);
2022-06-04 16:42:27 +02:00
2022-06-05 21:03:22 +02:00
test_create_account(&mut t).await;
2022-06-04 16:42:27 +02:00
2022-06-05 21:03:22 +02:00
testx::db_rollback!(t);
2022-06-04 16:42:27 +02:00
}
2022-06-04 16:55:29 +02:00
#[actix::test]
async fn find_by_jti() {
2022-06-05 21:03:22 +02:00
testx::db_t!(t);
2022-06-04 16:55:29 +02:00
2022-06-05 21:03:22 +02:00
let original = test_create_token_extended(&mut t, None, None, None, None, None).await;
2022-06-04 16:55:29 +02:00
2022-06-05 21:03:22 +02:00
let found = super::token_by_jti(
TokenByJti {
2022-06-04 16:55:29 +02:00
jti: original.jwt_id,
2022-06-05 21:03:22 +02:00
},
&mut t,
)
.await
.unwrap();
2022-06-04 16:55:29 +02:00
2022-06-05 21:03:22 +02:00
testx::db_rollback!(t);
2022-06-04 16:55:29 +02:00
assert_eq!(found, original);
}
#[actix::test]
async fn find_by_jti_expired() {
2022-06-05 21:03:22 +02:00
testx::db_t!(t);
2022-06-04 16:55:29 +02:00
let original = test_create_token_extended(
2022-06-05 21:03:22 +02:00
&mut t,
2022-06-04 16:55:29 +02:00
None,
None,
None,
None,
Some((chrono::Utc::now() - chrono::Duration::seconds(1)).naive_utc()),
)
.await;
2022-06-05 21:03:22 +02:00
let found = super::token_by_jti(
TokenByJti {
2022-06-04 16:55:29 +02:00
jti: original.jwt_id,
2022-06-05 21:03:22 +02:00
},
&mut t,
)
.await;
2022-06-04 16:55:29 +02:00
2022-06-05 21:03:22 +02:00
testx::db_rollback!(t);
2022-06-04 16:55:29 +02:00
assert_eq!(found, Err(crate::Error::Token(super::Error::Jti)));
}
2022-06-04 16:05:18 +02:00
}