rusty-bever/src/auth/jwt.rs

119 lines
3.0 KiB
Rust
Raw Normal View History

2021-08-21 18:05:16 +02:00
use chrono::Utc;
2021-09-01 12:50:33 +02:00
use diesel::PgConnection;
2021-08-21 13:46:41 +02:00
use hmac::{Hmac, NewMac};
use jwt::SignWithKey;
2021-08-21 18:05:16 +02:00
use rand::{thread_rng, Rng};
2021-08-21 21:42:36 +02:00
use serde::{Deserialize, Serialize};
2021-08-21 13:46:41 +02:00
use sha2::Sha256;
2021-08-20 23:09:22 +02:00
2021-08-22 16:45:01 +02:00
use crate::{
2021-08-29 21:15:10 +02:00
db,
2021-08-29 20:30:33 +02:00
errors::{RbError, RbResult},
2021-08-30 15:28:01 +02:00
RbJwtConf,
2021-08-22 16:45:01 +02:00
};
2021-08-21 16:45:41 +02:00
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
2021-08-22 16:45:01 +02:00
pub struct JWTResponse
{
2021-08-21 13:46:41 +02:00
token: String,
2021-08-21 18:05:16 +02:00
refresh_token: String,
2021-08-21 13:46:41 +02:00
}
2021-08-21 21:42:36 +02:00
#[derive(Serialize, Deserialize)]
2021-08-22 16:45:01 +02:00
pub struct Claims
{
2021-08-21 21:42:36 +02:00
pub id: uuid::Uuid,
pub username: String,
pub admin: bool,
pub exp: i64,
}
2021-08-30 15:28:01 +02:00
pub fn generate_jwt_token(
conn: &PgConnection,
jwt: &RbJwtConf,
2021-09-01 12:50:33 +02:00
user: &db::User,
2021-08-30 15:28:01 +02:00
) -> RbResult<JWTResponse>
2021-08-22 16:45:01 +02:00
{
2021-08-30 15:28:01 +02:00
let key: Hmac<Sha256> = Hmac::new_from_slice(jwt.key.as_bytes())
.map_err(|_| RbError::Custom("Couldn't create Hmac key."))?;
let current_time = Utc::now();
2021-08-21 13:46:41 +02:00
// Create the claims
2021-08-21 21:42:36 +02:00
let claims = Claims {
id: user.id,
username: user.username.clone(),
admin: user.admin,
2021-08-30 15:28:01 +02:00
exp: current_time.timestamp() + jwt.refresh_token_expire,
2021-08-21 21:42:36 +02:00
};
2021-08-21 13:46:41 +02:00
// Sign the claims into a new token
2021-08-21 18:05:16 +02:00
let token = claims
.sign_with_key(&key)
.map_err(|_| RbError::Custom("Couldn't sign JWT."))?;
2021-08-21 16:45:41 +02:00
// Generate a random refresh token
2021-08-30 15:28:01 +02:00
let mut refresh_token = vec![0u8; jwt.refresh_token_size];
2021-08-21 16:45:41 +02:00
thread_rng().fill(&mut refresh_token[..]);
2021-08-21 21:42:36 +02:00
let refresh_expire =
2021-08-30 15:28:01 +02:00
(current_time + chrono::Duration::seconds(jwt.refresh_token_expire)).naive_utc();
2021-08-21 16:45:41 +02:00
// Store refresh token in database
2021-08-29 21:15:10 +02:00
db::tokens::create(
conn,
2021-09-01 12:50:33 +02:00
&db::NewRefreshToken {
2021-08-21 18:05:16 +02:00
token: refresh_token.to_vec(),
user_id: user.id,
2021-08-21 21:42:36 +02:00
expires_at: refresh_expire,
2021-08-29 21:15:10 +02:00
},
)?;
2021-08-21 16:45:41 +02:00
Ok(JWTResponse {
2021-08-21 18:05:16 +02:00
token,
refresh_token: base64::encode(refresh_token),
2021-08-21 16:45:41 +02:00
})
2021-08-21 13:46:41 +02:00
}
2021-08-21 18:05:16 +02:00
2021-08-30 15:28:01 +02:00
pub fn refresh_token(
conn: &PgConnection,
jwt: &RbJwtConf,
refresh_token: &str,
) -> RbResult<JWTResponse>
2021-08-22 16:45:01 +02:00
{
let token_bytes =
base64::decode(refresh_token).map_err(|_| RbError::AuthInvalidRefreshToken)?;
2021-08-22 10:42:58 +02:00
// First, we request the token from the database to see if it's really a valid token
2021-08-29 21:15:10 +02:00
let (token_entry, user) =
db::tokens::find_with_user(conn, &token_bytes).ok_or(RbError::AuthInvalidRefreshToken)?;
2021-08-22 10:42:58 +02:00
// If we see that the token has already been used before, we block the user.
if token_entry.last_used_at.is_some() {
2021-09-01 12:50:33 +02:00
// If we fail to block the user, the end user must know
if let Err(err) = db::users::block(conn, token_entry.user_id) {
return Err(err);
}
2021-08-22 10:42:58 +02:00
return Err(RbError::AuthDuplicateRefreshToken);
2021-08-22 10:42:58 +02:00
}
2021-09-05 09:59:16 +02:00
// Then we check if the user is blocked
if user.blocked {
return Err(RbError::AuthBlockedUser);
}
2021-08-22 13:14:19 +02:00
// Now we check if the token has already expired
let cur_time = Utc::now().naive_utc();
if token_entry.expires_at < cur_time {
return Err(RbError::AuthTokenExpired);
2021-08-22 13:14:19 +02:00
}
2021-08-22 10:42:58 +02:00
// We update the last_used_at value for the refresh token
2021-09-01 12:50:33 +02:00
db::tokens::update_last_used_at(conn, &token_entry.token, cur_time)?;
2021-08-22 10:42:58 +02:00
2021-08-30 15:28:01 +02:00
generate_jwt_token(conn, jwt, &user)
2021-08-22 10:42:58 +02:00
}