Updated refresh token endpoint to enable rotation
This commit is contained in:
@@ -8,13 +8,11 @@ DATABASE_PORT=5432
|
||||
|
||||
ACCESS_TOKEN_PRIVATE_KEY=
|
||||
ACCESS_TOKEN_PUBLIC_KEY=
|
||||
ACCESS_TOKEN_EXPIRED_IN=15m
|
||||
ACCESS_TOKEN_MAXAGE=15
|
||||
ACCESS_TOKEN_MAXAGE=5
|
||||
|
||||
REFRESH_TOKEN_PRIVATE_KEY=
|
||||
REFRESH_TOKEN_PUBLIC_KEY=
|
||||
REFRESH_TOKEN_EXPIRED_IN=60m
|
||||
REFRESH_TOKEN_MAXAGE=60
|
||||
REFRESH_TOKEN_MAXAGE=30
|
||||
|
||||
REDIS_HOST=localhost
|
||||
REDIS_PORT=6379
|
||||
|
||||
@@ -150,11 +150,10 @@ impl FromRequest for JwtAuth {
|
||||
};
|
||||
let user_email = match conn.get::<_, String>(access_token_uuid.clone().to_string()) {
|
||||
Ok(result) => result,
|
||||
Err(err) => {
|
||||
error!("Failed to get access token from redis: {}", err);
|
||||
Err(_) => {
|
||||
return ready(Err(ActixError::from(ServiceError {
|
||||
status: 500,
|
||||
message: format!("Failed to get access token from redis: {}", err)
|
||||
status: 404,
|
||||
message: format!("Access token was not found")
|
||||
})))
|
||||
}
|
||||
};
|
||||
@@ -163,9 +162,9 @@ impl FromRequest for JwtAuth {
|
||||
Ok(user) => {
|
||||
ready(Ok(JwtAuth { token: access_token_uuid, user: user.into() }))
|
||||
}
|
||||
Err(err) => return ready(Err(ActixError::from(ServiceError {
|
||||
status: 500,
|
||||
message: format!("Failed to get user from db: {}", err)
|
||||
Err(_) => return ready(Err(ActixError::from(ServiceError {
|
||||
status: 404,
|
||||
message: format!("User was not found")
|
||||
})))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ use std::env;
|
||||
use actix_web::{get, post, web, HttpResponse, ResponseError, cookie::{Cookie, time::Duration}, HttpRequest};
|
||||
use log::error;
|
||||
use redis::AsyncCommands;
|
||||
use serde::{Serialize, Deserialize};
|
||||
use siren::ServiceError;
|
||||
|
||||
use crate::{auth::{LoginRequest, RegisterUser, InsertUser, QueryUser, verify_password, JwtAuth, verify_token, generate_access_token, generate_refresh_token}, db};
|
||||
@@ -124,8 +125,21 @@ async fn login(request: web::Json<LoginRequest>) -> HttpResponse {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct RefreshParams {
|
||||
refresh_token_rotation: Option<bool>
|
||||
}
|
||||
|
||||
#[get("/refresh")]
|
||||
async fn refresh(req: HttpRequest) -> HttpResponse {
|
||||
let params = match web::Query::<RefreshParams>::from_query(req.query_string()) {
|
||||
Ok(params) => params,
|
||||
Err(err) => return ResponseError::error_response(&ServiceError {
|
||||
status: 422,
|
||||
message: err.to_string()
|
||||
})
|
||||
};
|
||||
|
||||
let refresh_token = match req.cookie("refresh_token") {
|
||||
Some(cookie) => cookie.value().to_string(),
|
||||
None => return ResponseError::error_response(&ServiceError {
|
||||
@@ -133,6 +147,7 @@ async fn refresh(req: HttpRequest) -> HttpResponse {
|
||||
message: "Refresh token not found".to_string()
|
||||
})
|
||||
};
|
||||
|
||||
let public_key = env::var("REFRESH_TOKEN_PUBLIC_KEY")
|
||||
.expect("REFRESH_TOKEN_PUBLIC_KEY must be set");
|
||||
let refresh_token_details = match verify_token(&refresh_token, &public_key) {
|
||||
@@ -151,9 +166,9 @@ async fn refresh(req: HttpRequest) -> HttpResponse {
|
||||
let redis_result: redis::RedisResult<String> = conn.get(refresh_token_details.token_uuid.to_string()).await;
|
||||
let email = match redis_result {
|
||||
Ok(email) => email,
|
||||
Err(err) => return ResponseError::error_response(&ServiceError {
|
||||
status: 500,
|
||||
message: format!("Failed to get refresh token from redis: {}", err)
|
||||
Err(_) => return ResponseError::error_response(&ServiceError {
|
||||
status: 404,
|
||||
message: format!("Refresh token was not found")
|
||||
})
|
||||
};
|
||||
|
||||
@@ -166,6 +181,23 @@ async fn refresh(req: HttpRequest) -> HttpResponse {
|
||||
return ResponseError::error_response(&err)
|
||||
}
|
||||
};
|
||||
|
||||
// Delete old auth token if it exists
|
||||
match req.cookie("access_token") {
|
||||
Some(cookie) => {
|
||||
let access_token = cookie.value().to_string();
|
||||
let public_key = env::var("ACCESS_TOKEN_PUBLIC_KEY")
|
||||
.expect("ACCESS_TOKEN_PUBLIC_KEY must be set");
|
||||
match verify_token(&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")
|
||||
@@ -194,10 +226,54 @@ async fn refresh(req: HttpRequest) -> HttpResponse {
|
||||
|
||||
let access_token_uuid = uuid::Uuid::parse_str(&access_token_details.token_uuid.to_string()).unwrap();
|
||||
|
||||
HttpResponse::Ok()
|
||||
// Refresh the refresh token if requested
|
||||
let refresh_token_rotation = match params.refresh_token_rotation {
|
||||
Some(refresh_token_rotation) => refresh_token_rotation,
|
||||
None => false
|
||||
};
|
||||
if refresh_token_rotation {
|
||||
// Delete the old refresh token
|
||||
let _: redis::RedisResult<()> = conn.del(refresh_token_details.token_uuid.to_string()).await;
|
||||
|
||||
let refresh_token_details = match generate_refresh_token(&refresh_token_details.email) {
|
||||
Ok(token_details) => token_details,
|
||||
Err(err) => {
|
||||
error!("Failed to generate refresh token: {}", err);
|
||||
return ResponseError::error_response(&err)
|
||||
}
|
||||
};
|
||||
|
||||
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 refresh_result: redis::RedisResult<()> = conn.set_ex(refresh_token_details.token_uuid.to_string(), &refresh_token_details.email, (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 refresh_cookie = Cookie::build("refresh_token", refresh_token_details.token.clone().unwrap())
|
||||
.path("/")
|
||||
.max_age(Duration::new(refresh_token_max_age * 60, 0))
|
||||
.http_only(true)
|
||||
.finish();
|
||||
|
||||
HttpResponse::Ok()
|
||||
.cookie(refresh_cookie)
|
||||
.cookie(access_cookie)
|
||||
.cookie(logged_in_cookie)
|
||||
.json(JwtAuth { token: access_token_uuid, user: query_user.into() })
|
||||
} else {
|
||||
HttpResponse::Ok()
|
||||
.cookie(access_cookie)
|
||||
.cookie(logged_in_cookie)
|
||||
.json(JwtAuth { token: access_token_uuid, user: query_user.into() })
|
||||
}
|
||||
},
|
||||
Err(err) => return ResponseError::error_response(&err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user