rusty-bever/src/auth.rs

165 lines
5.0 KiB
Rust
Raw Normal View History

2021-08-21 15:58:51 +02:00
use crate::errors::RBError;
2021-08-22 16:24:59 +02:00
use crate::db::{
users::{User, NewUser},
tokens::{RefreshToken, NewRefreshToken}
};
2021-08-21 16:45:41 +02:00
use crate::schema::refresh_tokens::dsl as refresh_tokens;
2021-08-21 18:05:16 +02:00
use crate::schema::users::dsl as users;
2021-08-20 23:09:22 +02:00
use argon2::verify_encoded;
2021-08-21 18:05:16 +02:00
use chrono::Utc;
2021-08-20 23:09:22 +02:00
use diesel::prelude::*;
2021-08-21 18:05:16 +02:00
use diesel::{insert_into, 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-21 15:58:51 +02:00
pub fn verify_user(conn: &PgConnection, username: &str, password: &str) -> crate::Result<User> {
2021-08-20 23:09:22 +02:00
// TODO handle non-"NotFound" Diesel errors accordingely
2021-08-21 15:58:51 +02:00
let user = users::users
2021-08-20 23:09:22 +02:00
.filter(users::username.eq(username))
.first::<User>(conn)
2021-08-21 15:58:51 +02:00
.map_err(|_| RBError::UnknownUser)?;
2021-08-20 23:09:22 +02:00
2021-08-21 18:05:16 +02:00
// Check if a user is blocked
if user.blocked {
return Err(RBError::BlockedUser);
}
2021-08-20 23:09:22 +02:00
match verify_encoded(user.password.as_str(), password.as_bytes()) {
Ok(true) => Ok(user),
2021-08-21 15:58:51 +02:00
_ => Err(RBError::InvalidPassword),
2021-08-20 23:09:22 +02:00
}
}
2021-08-21 13:46:41 +02:00
2021-08-21 16:45:41 +02:00
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
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)]
pub struct Claims {
pub id: uuid::Uuid,
pub username: String,
pub admin: bool,
pub exp: i64,
}
2021-08-21 16:45:41 +02:00
pub fn generate_jwt_token(conn: &PgConnection, user: &User) -> crate::Result<JWTResponse> {
2021-08-21 21:42:36 +02:00
let secret = std::env::var("JWT_KEY").map_err(|_| RBError::MissingJWTKey)?;
2021-08-21 22:41:38 +02:00
let key: Hmac<Sha256> =
Hmac::new_from_slice(secret.as_bytes()).map_err(|_| RBError::JWTCreationError)?;
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)
2021-08-21 22:21:42 +02:00
.map_err(|_| RBError::JWTCreationError)?;
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
// TODO add expires_at here (it's what's causing the errors)
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::JWTCreationError)?;
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
pub fn hash_password(password: &str) -> crate::Result<String> {
// Generate a random salt
let mut salt = [0u8; 64];
thread_rng().fill(&mut salt[..]);
// Encode the actual password
let config = argon2::Config::default();
argon2::hash_encoded(password.as_bytes(), &salt, &config).map_err(|_| RBError::PWSaltError)
}
2021-08-21 21:42:36 +02:00
pub fn create_admin_user(
conn: &PgConnection,
username: &str,
password: &str,
) -> crate::Result<bool> {
2021-08-21 18:05:16 +02:00
let pass_hashed = hash_password(password)?;
let new_user = NewUser {
2021-08-21 21:42:36 +02:00
username: username.to_string(),
password: pass_hashed,
admin: true,
};
2021-08-21 18:05:16 +02:00
insert_into(users::users)
.values(&new_user)
.on_conflict(users::username)
.do_update()
.set(&new_user)
2021-08-21 21:42:36 +02:00
.execute(conn)
.map_err(|_| RBError::AdminCreationError)?;
2021-08-21 18:05:16 +02:00
Ok(true)
}
2021-08-22 10:42:58 +02:00
pub fn refresh_token(conn: &PgConnection, refresh_token: &str) -> crate::Result<JWTResponse> {
let token_bytes = base64::decode(refresh_token).map_err(|_| RBError::InvalidRefreshToken)?;
// 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)
2021-08-22 10:42:58 +02:00
.map_err(|_| RBError::InvalidRefreshToken)?;
// 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::DBError)?;
return Err(RBError::DuplicateRefreshToken);
}
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::TokenExpired);
}
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::DBError)?;
generate_jwt_token(conn, &user)
}