Updated files, will be switching to sessions

This commit is contained in:
Benjamin Sherriff
2024-01-30 14:19:59 -05:00
parent 40a45275d6
commit ca9270f3a7
7 changed files with 192 additions and 188 deletions

View File

@@ -6,7 +6,9 @@ use redis::AsyncCommands;
use serde::{Serialize, Deserialize};
use siren::ServiceError;
use crate::{auth::{verify_password, AccessToken, InsertUser, JwtAuth, LoginRequest, QueryUser, RefreshToken, RegisterUser}, storage};
use crate::{auth::{InsertUser, JwtAuth, LoginRequest, QueryUser, RefreshToken, RegisterUser}, storage};
use super::verify_hash;
#[post("/register")]
async fn register(user: web::Json<RegisterUser>) -> HttpResponse {
@@ -38,87 +40,79 @@ async fn login(request: HttpRequest, login_request: web::Json<LoginRequest>) ->
let query_user = match QueryUser::get_by_email(&email) {
Ok(query_user) => query_user,
Err(err) => return ResponseError::error_response(&err)
};
let hash = &query_user.hash;
let password = login_request.password.as_bytes();
match verify_password(hash, password) {
Ok(_) => {
let access_token = match AccessToken::new(&email) {
Ok(token_details) => token_details,
Err(err) => {
error!("Failed to generate access token: {}", err);
return ResponseError::error_response(&err)
}
};
let refresh_token = RefreshToken::new(&email, &ip_address);
let mut conn = match storage::redis_async_connection().await {
Ok(conn) => conn,
Err(err) => {
error!("Failed to get redis connection: {}", err);
return ResponseError::error_response(&err)
}
};
let access_token_max_age = env::var("ACCESS_TOKEN_MAXAGE")
.expect("ACCESS_TOKEN_MAXAGE must be set")
.parse::<i64>()
.expect("ACCESS_TOKEN_MAXAGE must be an integer");
let refresh_token_max_age = env::var("REFRESH_TOKEN_MAXAGE")
.expect("REFRESH_TOKEN_MAXAGE must be set")
.parse::<i64>()
.expect("REFRESH_TOKEN_MAXAGE must be an integer");
let access_result: redis::RedisResult<()> = conn.set_ex(access_token.token_uuid.to_string(), &serde_json::to_string(&access_token).unwrap(), (access_token_max_age * 60) as usize).await;
if let Err(err) = access_result {
error!("Failed to set access token in redis: {}", err);
return ResponseError::error_response(&ServiceError {
status: 500,
message: format!("Failed to set access token in redis: {}", err)
})
};
let refresh_result: redis::RedisResult<()> = conn.set_ex(refresh_token.hash.to_string(), &serde_json::to_string(&refresh_token).unwrap(), (refresh_token_max_age * 60) as usize).await;
if let Err(err) = refresh_result {
error!("Failed to set refresh token in redis: {}", err);
return ResponseError::error_response(&ServiceError {
status: 500,
message: format!("Failed to set refresh token in redis: {}", err)
})
};
let access_cookie = Cookie::build("access_token", access_token.token.clone().unwrap())
.path("/")
.max_age(Duration::new(access_token_max_age * 60, 0))
.http_only(true)
.secure(true)
.finish();
let refresh_cookie = Cookie::build("refresh_token", refresh_token.hash.clone())
.path("/")
.max_age(Duration::new(refresh_token_max_age * 60, 0))
.http_only(true)
.secure(true)
.finish();
let logged_in_cookie = Cookie::build("logged_in", "true")
.path("/")
.max_age(Duration::new(access_token_max_age * 60, 0))
.http_only(false)
.finish();
let access_token_uuid = uuid::Uuid::parse_str(&access_token.token_uuid.to_string()).unwrap();
HttpResponse::Ok()
.cookie(access_cookie)
.cookie(refresh_cookie)
.cookie(logged_in_cookie)
.json(JwtAuth { token: access_token_uuid, user: query_user.into() })
},
Err(err) => ResponseError::error_response(&ServiceError {
Err(_) => return ResponseError::error_response(&ServiceError {
status: 401,
message: err.to_string()
message: "The email or password was incorrect.".to_string()
})
};
// Verify password
if verify_hash(&login_request.password, &query_user.hash) {
let mut refresh_token = RefreshToken::new(&email, &ip_address);
let access_token = match refresh_token.create_access_token() {
Ok(token) => token,
Err(err) => {
error!("Failed to generate access token: {}", err);
return ResponseError::error_response(&err)
}
};
let mut conn = match storage::redis_async_connection().await {
Ok(conn) => conn,
Err(err) => {
error!("Failed to get redis connection: {}", err);
return ResponseError::error_response(&err)
}
};
let access_token_max_age = env::var("ACCESS_TOKEN_MAXAGE")
.expect("ACCESS_TOKEN_MAXAGE must be set")
.parse::<i64>()
.expect("ACCESS_TOKEN_MAXAGE must be an integer");
let refresh_token_max_age = env::var("REFRESH_TOKEN_MAXAGE")
.expect("REFRESH_TOKEN_MAXAGE must be set")
.parse::<i64>()
.expect("REFRESH_TOKEN_MAXAGE must be an integer");
let access_result: redis::RedisResult<()> = conn.set_ex(access_token.id.to_string(), &serde_json::to_string(&access_token).unwrap(), (access_token_max_age * 60) as usize).await;
if let Err(err) = access_result {
error!("Failed to set access token in redis: {}", err);
return ResponseError::error_response(&ServiceError::from(err))
};
let refresh_result: redis::RedisResult<()> = conn.set_ex(refresh_token.id.to_string(), &serde_json::to_string(&refresh_token).unwrap(), (refresh_token_max_age * 60) as usize).await;
if let Err(err) = refresh_result {
error!("Failed to set refresh token in redis: {}", err);
return ResponseError::error_response(&ServiceError::from(err))
};
let access_cookie = Cookie::build("access_token", access_token.token.clone().unwrap())
.path("/")
.max_age(Duration::new(access_token_max_age * 60, 0))
.http_only(true)
.secure(true)
.finish();
let refresh_cookie = Cookie::build("refresh_token", refresh_token.id.clone())
.path("/")
.max_age(Duration::new(refresh_token_max_age * 60, 0))
.http_only(true)
.secure(true)
.finish();
let logged_in_cookie = Cookie::build("logged_in", "true")
.path("/")
.max_age(Duration::new(access_token_max_age * 60, 0))
.http_only(false)
.finish();
HttpResponse::Ok()
.cookie(access_cookie)
.cookie(refresh_cookie)
.cookie(logged_in_cookie)
.json(JwtAuth { id: access_token.id, user: query_user.into() })
} else {
return ResponseError::error_response(&ServiceError {
status: 401,
message: "The email or password was incorrect.".to_string()
})
}
}
@@ -139,7 +133,7 @@ async fn refresh(req: HttpRequest) -> HttpResponse {
})
};
let refresh_token_hash = match req.cookie("refresh_token") {
let refresh_token_string = match req.cookie("refresh_token") {
Some(cookie) => cookie.value().to_string(),
None => return ResponseError::error_response(&ServiceError {
status: 401,
@@ -155,23 +149,26 @@ async fn refresh(req: HttpRequest) -> HttpResponse {
}
};
let refresh_token: RefreshToken = match conn.get::<_, String>(refresh_token_hash.clone()).await {
Ok(result) => match serde_json::from_str(&result) {
Ok(result) => result,
let mut refresh_token: RefreshToken = match conn.get::<_, String>(refresh_token_string.clone()).await {
Ok(result) => match serde_json::from_str::<RefreshToken>(&result) {
Ok(result) => {
if verify_hash(&ip_address, &result.ip_address) {
result
} else {
return ResponseError::error_response(&ServiceError {
status: 401,
message: "Refresh token is invalid".to_string()
})
}
},
Err(err) => {
error!("Failed to deserialize refresh token: {}", err);
return ResponseError::error_response(&ServiceError {
status: 500,
message: format!("Failed to deserialize refresh token: {}", err)
})
return ResponseError::error_response(&ServiceError::from(err))
}
},
Err(err) => {
error!("Failed to get refresh token from redis: {}", err);
return ResponseError::error_response(&ServiceError {
status: 500,
message: format!("Failed to get refresh token from redis: {}", err)
})
return ResponseError::error_response(&ServiceError::from(err))
}
};
@@ -179,46 +176,33 @@ async fn refresh(req: HttpRequest) -> HttpResponse {
match QueryUser::get_by_email(&email) {
Ok(query_user) => {
let access_token_details = match AccessToken::new(&email) {
Ok(token_details) => token_details,
// Revoke all old access tokens
for id in refresh_token.tokens {
let _: redis::RedisResult<()> = conn.del(id).await;
}
refresh_token.tokens = vec![];
// Create new access token
let access_token = match refresh_token.create_access_token() {
Ok(token) => token,
Err(err) => {
error!("Failed to generate access token: {}", err);
return ResponseError::error_response(&err)
}
};
// Delete old access token if it exists
match req.cookie("access_token") {
Some(cookie) => {
let access_token = cookie.value().to_string();
let keys_dir = env::var("KEYS_DIR_PATH").expect("KEYS_DIR_PATH must be set");
let public_key = std::fs::read_to_string(format!("{}/access_public_key.pem", keys_dir))
.expect("Unable to read access public key");
match AccessToken::decode(&access_token, &public_key) {
Ok(token_details) => {
let _: redis::RedisResult<()> = conn.del(token_details.token_uuid.to_string()).await;
},
Err(_) => {}
};
},
None => {}
};
let access_token_max_age = env::var("ACCESS_TOKEN_MAXAGE")
.expect("ACCESS_TOKEN_MAXAGE must be set")
.parse::<i64>()
.expect("ACCESS_TOKEN_MAXAGE must be an integer");
let access_result: redis::RedisResult<()> = conn.set_ex(access_token_details.token_uuid.to_string(), &serde_json::to_string(&access_token_details).unwrap(), (access_token_max_age * 60) as usize).await;
let access_result: redis::RedisResult<()> = conn.set_ex(access_token.id.to_string(), &serde_json::to_string(&access_token).unwrap(), (access_token_max_age * 60) as usize).await;
if let Err(err) = access_result {
error!("Failed to set access token in redis: {}", err);
return ResponseError::error_response(&ServiceError {
status: 500,
message: format!("Failed to set access token in redis: {}", err)
})
return ResponseError::error_response(&ServiceError::from(err))
};
let access_cookie = Cookie::build("access_token", access_token_details.token.clone().unwrap())
let access_cookie = Cookie::build("access_token", access_token.id.clone())
.path("/")
.max_age(Duration::new(access_token_max_age * 60, 0))
.http_only(true)
@@ -229,8 +213,6 @@ async fn refresh(req: HttpRequest) -> HttpResponse {
.max_age(Duration::new(access_token_max_age * 60, 0))
.http_only(false)
.finish();
let access_token_uuid = uuid::Uuid::parse_str(&access_token_details.token_uuid.to_string()).unwrap();
// Refresh the refresh token if requested
let refresh_token_rotation = match params.refresh_token_rotation {
@@ -238,8 +220,8 @@ async fn refresh(req: HttpRequest) -> HttpResponse {
None => false
};
if refresh_token_rotation {
// Delete the old refresh token
let _: redis::RedisResult<()> = conn.del(refresh_token.hash.to_string()).await;
// Delete the old refresh token from redis
let _: redis::RedisResult<()> = conn.del(refresh_token.id.to_string()).await;
let refresh_token = RefreshToken::new(&refresh_token.email, &ip_address);
let refresh_token_max_age = env::var("REFRESH_TOKEN_MAXAGE")
@@ -247,7 +229,8 @@ async fn refresh(req: HttpRequest) -> HttpResponse {
.parse::<i64>()
.expect("REFRESH_TOKEN_MAXAGE must be an integer");
let refresh_result: redis::RedisResult<()> = conn.set_ex(refresh_token.hash.to_string(), &serde_json::to_string(&refresh_token).unwrap(), (refresh_token_max_age * 60) as usize).await;
// Add the new refresh token to redis
let refresh_result: redis::RedisResult<()> = conn.set_ex(refresh_token.id.to_string(), &serde_json::to_string(&refresh_token).unwrap(), (refresh_token_max_age * 60) as usize).await;
if let Err(err) = refresh_result {
error!("Failed to set refresh token in redis: {}", err);
return ResponseError::error_response(&ServiceError {
@@ -256,7 +239,7 @@ async fn refresh(req: HttpRequest) -> HttpResponse {
})
};
let refresh_cookie = Cookie::build("refresh_token", refresh_token.hash.clone())
let refresh_cookie = Cookie::build("refresh_token", refresh_token.id.clone())
.path("/")
.max_age(Duration::new(refresh_token_max_age * 60, 0))
.http_only(true)
@@ -267,12 +250,12 @@ async fn refresh(req: HttpRequest) -> HttpResponse {
.cookie(refresh_cookie)
.cookie(access_cookie)
.cookie(logged_in_cookie)
.json(JwtAuth { token: access_token_uuid, user: query_user.into() })
.json(JwtAuth { id: access_token.id, user: query_user.into() })
} else {
HttpResponse::Ok()
.cookie(access_cookie)
.cookie(logged_in_cookie)
.json(JwtAuth { token: access_token_uuid, user: query_user.into() })
.json(JwtAuth { id: access_token.id, user: query_user.into() })
}
},
Err(err) => return ResponseError::error_response(&err)
@@ -299,7 +282,7 @@ async fn logout(req: HttpRequest, auth: JwtAuth) -> HttpResponse {
let access_result: redis::RedisResult<()> = conn.del(&[
refresh_token.to_string(),
auth.token.to_string()
auth.id.to_string()
]).await;
if let Err(err) = access_result {
error!("Failed to set access token in redis: {}", err);
@@ -312,11 +295,13 @@ async fn logout(req: HttpRequest, auth: JwtAuth) -> HttpResponse {
let access_cookie = Cookie::build("access_token", "")
.path("/")
.max_age(Duration::new(-1, 0))
.secure(true)
.http_only(true)
.finish();
let refresh_cookie = Cookie::build("refresh_token", "")
.path("/")
.max_age(Duration::new(-1, 0))
.secure(true)
.http_only(true)
.finish();
let logged_in_cookie = Cookie::build("logged_in", "")