2022-04-18 22:07:52 +02:00
|
|
|
use actix::Message;
|
2022-06-09 15:28:15 +02:00
|
|
|
use model::{AccountId, Audience, Token, TokenId};
|
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| {
|
2022-06-07 11:36:01 +02:00
|
|
|
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| {
|
2022-06-07 11:36:01 +02:00
|
|
|
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| {
|
2022-06-07 11:36:01 +02:00
|
|
|
tracing::error!("{e:?}");
|
2022-05-10 16:20:37 +02:00
|
|
|
crate::Error::Token(Error::Create)
|
|
|
|
})
|
|
|
|
}
|
2022-06-09 15:28:15 +02:00
|
|
|
#[derive(Message)]
|
|
|
|
#[rtype(result = "Result<Option<Token>>")]
|
|
|
|
pub struct DeleteToken {
|
|
|
|
pub token_id: TokenId,
|
|
|
|
}
|
|
|
|
|
|
|
|
db_async_handler!(DeleteToken, delete_token, Option<Token>, inner_delete_token);
|
|
|
|
|
|
|
|
pub(crate) async fn delete_token(
|
|
|
|
msg: DeleteToken,
|
|
|
|
t: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
|
|
|
) -> Result<Option<Token>> {
|
|
|
|
sqlx::query_as(r#"
|
|
|
|
DELETE FROM tokens
|
|
|
|
WHERE id = $1
|
|
|
|
RETURNING id, customer_id, role, issuer, subject, audience, expiration_time, not_before_time, issued_at_time, jwt_id
|
|
|
|
"#)
|
|
|
|
.bind(msg.token_id)
|
|
|
|
.fetch_optional(t)
|
|
|
|
.await
|
|
|
|
.map_err(|e| {
|
|
|
|
tracing::error!("{e:?}");
|
|
|
|
crate::Error::Token(Error::Jti)
|
|
|
|
})
|
|
|
|
}
|
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-09 15:28:15 +02:00
|
|
|
testx::db_t_ref!(t);
|
2022-06-05 21:03:22 +02:00
|
|
|
|
|
|
|
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-09 15:28:15 +02:00
|
|
|
testx::db_t_ref!(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-09 15:28:15 +02:00
|
|
|
testx::db_t_ref!(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-09 15:28:15 +02:00
|
|
|
testx::db_t_ref!(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
|
|
|
}
|