add using refresh token to get access token
This commit is contained in:
BIN
db/auth.sqlite
BIN
db/auth.sqlite
Binary file not shown.
Binary file not shown.
Binary file not shown.
6
refrech_token.sql
Normal file
6
refrech_token.sql
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
id INT
|
||||||
|
user_id INTEGER NOT NULL,
|
||||||
|
token_hash TEXT NOT NULL,
|
||||||
|
device_id TEXT NOT NULL,
|
||||||
|
user_agent TEXT NOT NULL,
|
||||||
|
hotel_id TEXT
|
||||||
@@ -22,7 +22,7 @@ use crate::utils::auth::AuthClaims;
|
|||||||
|
|
||||||
pub async fn create_conversation(
|
pub async fn create_conversation(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
AuthClaims {user_id, hotel_id, username}: AuthClaims,
|
AuthClaims {user_id, hotel_id}: AuthClaims,
|
||||||
CreateConversationPayload(payload): CreateConversationPayload
|
CreateConversationPayload(payload): CreateConversationPayload
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
|
|
||||||
@@ -48,7 +48,7 @@ pub async fn create_conversation(
|
|||||||
|
|
||||||
pub async fn add_user_to_conv(
|
pub async fn add_user_to_conv(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
AuthClaims {user_id, hotel_id,username}: AuthClaims,
|
AuthClaims {user_id, hotel_id}: AuthClaims,
|
||||||
AddUserConversationPayload(payload):AddUserConversationPayload
|
AddUserConversationPayload(payload):AddUserConversationPayload
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
|
|
||||||
@@ -102,7 +102,7 @@ pub async fn add_user_to_conv(
|
|||||||
|
|
||||||
pub async fn send_message(
|
pub async fn send_message(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
AuthClaims {user_id, hotel_id,username}: AuthClaims,
|
AuthClaims {user_id, hotel_id}: AuthClaims,
|
||||||
SendMessagePayload(payload):SendMessagePayload
|
SendMessagePayload(payload):SendMessagePayload
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
|
|
||||||
@@ -187,7 +187,7 @@ struct Message {
|
|||||||
|
|
||||||
pub async fn get_message(
|
pub async fn get_message(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
AuthClaims {user_id, hotel_id,username}: AuthClaims,
|
AuthClaims {user_id, hotel_id}: AuthClaims,
|
||||||
GetMessagesPayload(payload):GetMessagesPayload
|
GetMessagesPayload(payload):GetMessagesPayload
|
||||||
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ use crate::utils::{auth::AuthClaims, db_pool::AppState};
|
|||||||
pub async fn create_inventory_item(
|
pub async fn create_inventory_item(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
Path((item_name, item_amount)): Path<(String, i32)>,
|
Path((item_name, item_amount)): Path<(String, i32)>,
|
||||||
AuthClaims{ user_id, hotel_id, username}: AuthClaims,
|
AuthClaims{ user_id, hotel_id}: AuthClaims,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
|
|
||||||
let pool = state.hotel_pools.get_pool(hotel_id);
|
let pool = state.hotel_pools.get_pool(hotel_id);
|
||||||
@@ -37,7 +37,7 @@ pub async fn create_inventory_item(
|
|||||||
pub async fn update_inventory_item(
|
pub async fn update_inventory_item(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
Path((item_id, item_amount)): Path<(i32, i32)>,
|
Path((item_id, item_amount)): Path<(i32, i32)>,
|
||||||
AuthClaims { user_id, hotel_id, username }: AuthClaims,
|
AuthClaims { user_id, hotel_id }: AuthClaims,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
|
|
||||||
//TODO: make better error handling :
|
//TODO: make better error handling :
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ pub async fn hello_rooms() -> String {
|
|||||||
|
|
||||||
pub async fn fake_db_update(
|
pub async fn fake_db_update(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
AuthClaims { user_id, hotel_id, username }: AuthClaims,
|
AuthClaims { user_id, hotel_id }: AuthClaims,
|
||||||
Path(room_id): Path<i32>,
|
Path(room_id): Path<i32>,
|
||||||
UpdateRoomPayload(payload): UpdateRoomPayload,
|
UpdateRoomPayload(payload): UpdateRoomPayload,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
@@ -52,7 +52,7 @@ pub async fn fake_db_update(
|
|||||||
pub async fn clean_db_update(
|
pub async fn clean_db_update(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
Path(room_id): Path<i32>,
|
Path(room_id): Path<i32>,
|
||||||
AuthClaims { user_id, hotel_id, username }: AuthClaims,
|
AuthClaims { user_id, hotel_id }: AuthClaims,
|
||||||
UpdateRoomPayload(payload): UpdateRoomPayload,
|
UpdateRoomPayload(payload): UpdateRoomPayload,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,6 @@
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use axum::{
|
use axum::{
|
||||||
body::{to_bytes, Body},
|
body::{to_bytes, Body}, extract::{Extension, FromRequest, FromRequestParts, Path, State}, http::{header::{HeaderValue, SET_COOKIE}, request::Parts, Request as HttpRequest, StatusCode }, middleware::Next, response::{IntoResponse, IntoResponseParts, Response}, Json
|
||||||
http::{Request as HttpRequest, StatusCode, header::{SET_COOKIE, HeaderValue} },
|
|
||||||
http::request::Parts,
|
|
||||||
middleware::Next,
|
|
||||||
response::{Response, IntoResponse},
|
|
||||||
Json,
|
|
||||||
extract::{Path, State, FromRequest, FromRequestParts, Extension}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use axum_extra::extract::TypedHeader;
|
use axum_extra::extract::TypedHeader;
|
||||||
@@ -44,11 +38,11 @@ pub struct JwtKeys {
|
|||||||
pub async fn token_tester(
|
pub async fn token_tester(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
//Extension(keys): Extension<JwtKeys>,
|
//Extension(keys): Extension<JwtKeys>,
|
||||||
AuthClaims { user_id, hotel_id, username }: AuthClaims,
|
AuthClaims { user_id, hotel_id }: AuthClaims,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
format!(
|
format!(
|
||||||
"Hello {} (user_id: {}) from hotel {}",
|
"(user_id: {}) from hotel {}",
|
||||||
username, user_id, hotel_id
|
user_id, hotel_id
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,7 +52,7 @@ pub struct AuthUser(pub Claims); //??
|
|||||||
pub struct AuthClaims {
|
pub struct AuthClaims {
|
||||||
pub user_id: i32,
|
pub user_id: i32,
|
||||||
pub hotel_id: i32,
|
pub hotel_id: i32,
|
||||||
pub username: String,
|
//pub username: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S> FromRequestParts<S> for AuthClaims
|
impl<S> FromRequestParts<S> for AuthClaims
|
||||||
@@ -96,7 +90,7 @@ where
|
|||||||
Ok(AuthClaims {
|
Ok(AuthClaims {
|
||||||
user_id: token_data.claims.id,
|
user_id: token_data.claims.id,
|
||||||
hotel_id: token_data.claims.hotel_id,
|
hotel_id: token_data.claims.hotel_id,
|
||||||
username: token_data.claims.username,
|
//username: token_data.claims.username,
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -381,7 +375,7 @@ pub async fn clean_auth_loging(
|
|||||||
|
|
||||||
let claims = serde_json::json!({
|
let claims = serde_json::json!({
|
||||||
"id": user_id,
|
"id": user_id,
|
||||||
"hotel_id": payload.hotel_id,
|
"hotel_id": hotel_id,
|
||||||
"username": payload.username,
|
"username": payload.username,
|
||||||
"exp": expiration
|
"exp": expiration
|
||||||
});
|
});
|
||||||
@@ -402,7 +396,7 @@ pub struct CreateRefreshTokenValue {
|
|||||||
pub username: String,
|
pub username: String,
|
||||||
pub password: String,
|
pub password: String,
|
||||||
pub device_id: Uuid,
|
pub device_id: Uuid,
|
||||||
pub timestamp: Option<String>,
|
//pub timestamp: Option<String>,
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -424,28 +418,29 @@ pub async fn create_refresh_token(
|
|||||||
let mut bytes = [0u8; 64];
|
let mut bytes = [0u8; 64];
|
||||||
OsRng.fill_bytes(&mut bytes);
|
OsRng.fill_bytes(&mut bytes);
|
||||||
|
|
||||||
|
let raw_token = Uuid::new_v4().to_string();
|
||||||
|
|
||||||
let hashed_token = argon2
|
let hashed_token = argon2
|
||||||
.hash_password(&bytes, &salt)
|
.hash_password(raw_token.as_bytes(), &salt)
|
||||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?
|
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
||||||
let raw_token = general_purpose::STANDARD.encode(&bytes);
|
|
||||||
|
|
||||||
let conn = state.logs_pool.get()
|
let conn = state.logs_pool.get()
|
||||||
.map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "DB connection error".to_string()))?;
|
.map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "DB connection error".to_string()))?;
|
||||||
|
|
||||||
let user_row = conn.query_row(
|
let user_row = conn.query_row(
|
||||||
"SELECT id, password FROM users WHERE username = ?1",
|
"SELECT id, password, hotel_id FROM users WHERE username = ?1",
|
||||||
params![&payload.username],
|
params![&payload.username],
|
||||||
|row| {
|
|row| {
|
||||||
let user_id: i32 = row.get(0)?;
|
let user_id: i32 = row.get(0)?;
|
||||||
let password: String = row.get(1)?;
|
let password: String = row.get(1)?;
|
||||||
Ok((user_id, password))
|
let hotel_id: i32 = row.get(2)?;
|
||||||
|
Ok((user_id, password, hotel_id))
|
||||||
},
|
},
|
||||||
).optional()
|
).optional()
|
||||||
.map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "DB get user id error".to_string()))?;
|
.map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "DB get user id error".to_string()))?;
|
||||||
|
|
||||||
let (user_id, stored_hash) = user_row
|
let (user_id, stored_hash, hotel_id) = user_row
|
||||||
.ok_or((StatusCode::NOT_FOUND, "User not found".to_string()))?;
|
.ok_or((StatusCode::NOT_FOUND, "User not found".to_string()))?;
|
||||||
|
|
||||||
if !verify_password(&payload.password, &stored_hash) {
|
if !verify_password(&payload.password, &stored_hash) {
|
||||||
@@ -453,8 +448,8 @@ pub async fn create_refresh_token(
|
|||||||
}
|
}
|
||||||
|
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"INSERT INTO refresh_token (user_id, token_hash, device_id, user_agent) VALUES (?1, ?2, ?3, ?4)",
|
"INSERT INTO refresh_token (user_id, token_hash, device_id, user_agent, hotel_id) VALUES (?1, ?2, ?3, ?4, ?5)",
|
||||||
params![user_id, hashed_token, device_id_str, user_agent_str],
|
params![user_id, hashed_token, device_id_str, user_agent_str, hotel_id],
|
||||||
)
|
)
|
||||||
.map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "DB insert error".to_string()))?;
|
.map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "DB insert error".to_string()))?;
|
||||||
|
|
||||||
@@ -469,6 +464,84 @@ pub async fn create_refresh_token(
|
|||||||
Ok(response) // ← Wrap in Ok()
|
Ok(response) // ← Wrap in Ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct LoginRefreshTokenValues{
|
||||||
|
device_id: Uuid,
|
||||||
|
refresh_token: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn login_refresh_token (
|
||||||
|
State(state): State<AppState>,
|
||||||
|
Extension(keys): Extension<JwtKeys>,
|
||||||
|
user_agent: Option<TypedHeader<UserAgent>>,
|
||||||
|
Json(payload): Json<LoginRefreshTokenValues>
|
||||||
|
) -> impl IntoResponse {
|
||||||
|
|
||||||
|
let conn = match state.logs_pool.get() {
|
||||||
|
Ok(c) => c,
|
||||||
|
Err(_) => return (StatusCode::INTERNAL_SERVER_ERROR, "DB connection error").into_response(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let user_agent_str = user_agent
|
||||||
|
.map(|ua| ua.to_string())
|
||||||
|
.unwrap_or_else(|| "Unknown".to_string());
|
||||||
|
|
||||||
|
let device_id_str = payload.device_id.to_string();
|
||||||
|
|
||||||
|
//"SELECT user_id, token_hash, hotel_id FROM refresh_token WHERE device_id = ?1 AND user_agent = ?2",
|
||||||
|
|
||||||
|
let device_row = match conn.query_row(
|
||||||
|
"SELECT user_id, token_hash, hotel_id FROM refresh_token WHERE device_id = ?1 AND user_agent = ?2",
|
||||||
|
params![&device_id_str, &user_agent_str],
|
||||||
|
|row| {
|
||||||
|
let user_id: i32 = row.get(0)?;
|
||||||
|
let token_hash: String = row.get(1)?;
|
||||||
|
let hotel_id: i32 = row.get(2)?;
|
||||||
|
//let displayname: String = row.get(3)?;
|
||||||
|
Ok((user_id, token_hash, hotel_id))
|
||||||
|
},
|
||||||
|
).optional() {
|
||||||
|
Ok(opt) => opt,
|
||||||
|
Err(_) => return (StatusCode::INTERNAL_SERVER_ERROR, "DB query error").into_response(),
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
let (user_id, token_hash, hotel_id) = match device_row {
|
||||||
|
Some(tuple) => tuple,
|
||||||
|
None => return (StatusCode::UNAUTHORIZED, "No matching device").into_response(),
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
if !verify_password(&payload.refresh_token, &token_hash) {
|
||||||
|
return (StatusCode::UNAUTHORIZED, "Invalid or mismatched token").into_response();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
let expiration = match chrono::Utc::now().checked_add_signed(chrono::Duration::hours(15)) {
|
||||||
|
Some(time) => time.timestamp() as usize,
|
||||||
|
None => {
|
||||||
|
// Handle overflow — probably a 500, since this should never happen
|
||||||
|
return (StatusCode::INTERNAL_SERVER_ERROR, "Time overflow".to_string()).into_response();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let claims = serde_json::json!({
|
||||||
|
"id": user_id,
|
||||||
|
"hotel_id": hotel_id,
|
||||||
|
//"username": payload.username,
|
||||||
|
"exp": expiration
|
||||||
|
});
|
||||||
|
|
||||||
|
let token = match encode(
|
||||||
|
&Header::default(),
|
||||||
|
&claims,
|
||||||
|
&keys.encoding
|
||||||
|
) {
|
||||||
|
Ok(t) => t,
|
||||||
|
Err(_) => return (StatusCode::INTERNAL_SERVER_ERROR, "JWT creation failed").into_response(),
|
||||||
|
};
|
||||||
|
Json(LoginResponse { token }).into_response()
|
||||||
|
}
|
||||||
|
|
||||||
fn internal_error<E: std::fmt::Display>(err: E) -> (StatusCode, String) {
|
fn internal_error<E: std::fmt::Display>(err: E) -> (StatusCode, String) {
|
||||||
(StatusCode::INTERNAL_SERVER_ERROR, format!("Internal error: {}", err))
|
(StatusCode::INTERNAL_SERVER_ERROR, format!("Internal error: {}", err))
|
||||||
|
|||||||
@@ -22,6 +22,6 @@ pub fn utils_routes() -> Router<AppState> {
|
|||||||
.route("/update_password", put(UpdatePassword))
|
.route("/update_password", put(UpdatePassword))
|
||||||
|
|
||||||
.route("/create_refresh", post(create_refresh_token))
|
.route("/create_refresh", post(create_refresh_token))
|
||||||
|
.route("/login_refresh_token", post(login_refresh_token))
|
||||||
//.with_state(state)
|
//.with_state(state)
|
||||||
}
|
}
|
||||||
@@ -89,7 +89,7 @@ async fn handle_socket(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn ws_handler(
|
pub async fn ws_handler(
|
||||||
AuthClaims {user_id, hotel_id,username}: AuthClaims,
|
AuthClaims {user_id, hotel_id}: AuthClaims,
|
||||||
ws: WebSocketUpgrade,
|
ws: WebSocketUpgrade,
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
//Path((hotel_id, user_id)): Path<(i32, i32)>,
|
//Path((hotel_id, user_id)): Path<(i32, i32)>,
|
||||||
|
|||||||
Reference in New Issue
Block a user