rusty-bever/src/auth/jwt.rs

118 lines
3.6 KiB
Rust
Raw Normal View History

2021-08-21 18:05:16 +02:00
use chrono::Utc;
2021-08-22 16:45:01 +02:00
use diesel::{insert_into, prelude::*, 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::{
db::{
tokens::{NewRefreshToken, RefreshToken},
2021-08-29 19:07:36 +02:00
users::User,
2021-08-22 16:45:01 +02:00
},
errors::RbError,
2021-08-22 16:45:01 +02:00
schema::{refresh_tokens::dsl as refresh_tokens, users::dsl as users},
};
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-22 16:45:01 +02:00
pub fn generate_jwt_token(conn: &PgConnection, user: &User) -> crate::Result<JWTResponse>
{
let secret = std::env::var("JWT_KEY").map_err(|_| RbError::Custom("Missing JWT key."))?;
let key: Hmac<Sha256> = Hmac::new_from_slice(secret.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-21 22:21:42 +02:00
exp: current_time.timestamp() + crate::JWT_EXP_SECONDS,
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-21 22:21:42 +02:00
let mut refresh_token = [0u8; crate::REFRESH_TOKEN_N_BYTES];
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-21 22:21:42 +02:00
(current_time + chrono::Duration::seconds(crate::REFRESH_TOKEN_EXP_SECONDS)).naive_utc();
2021-08-21 16:45:41 +02:00
// Store refresh token in database
2021-08-21 18:05:16 +02:00
insert_into(refresh_tokens::refresh_tokens)
.values(NewRefreshToken {
token: refresh_token.to_vec(),
user_id: user.id,
2021-08-21 21:42:36 +02:00
expires_at: refresh_expire,
2021-08-21 18:05:16 +02:00
})
.execute(conn)
.map_err(|_| RbError::Custom("Couldn't insert refresh token."))?;
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-22 16:45:01 +02:00
pub fn refresh_token(conn: &PgConnection, refresh_token: &str) -> crate::Result<JWTResponse>
{
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-22 13:14:19 +02:00
let (token_entry, user) = refresh_tokens::refresh_tokens
.inner_join(users::users)
2021-08-22 10:42:58 +02:00
.filter(refresh_tokens::token.eq(token_bytes))
2021-08-22 13:14:19 +02:00
.first::<(RefreshToken, User)>(conn)
.map_err(|_| 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() {
let target = users::users.filter(users::id.eq(token_entry.user_id));
diesel::update(target)
.set(users::blocked.eq(true))
.execute(conn)
.map_err(|_| RbError::Custom("Couldn't block user."))?;
2021-08-22 10:42:58 +02:00
return Err(RbError::AuthDuplicateRefreshToken);
2021-08-22 10:42:58 +02:00
}
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
let target = refresh_tokens::refresh_tokens.filter(refresh_tokens::token.eq(token_entry.token));
diesel::update(target)
2021-08-22 13:14:19 +02:00
.set(refresh_tokens::last_used_at.eq(cur_time))
2021-08-22 10:42:58 +02:00
.execute(conn)
.map_err(|_| RbError::Custom("Couldn't update last used time."))?;
2021-08-22 10:42:58 +02:00
generate_jwt_token(conn, &user)
}