multi-hotel-refactor #3
BIN
db/1.sqlite
BIN
db/1.sqlite
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -5,6 +5,7 @@ use axum::extract::{ws::{Message, WebSocket, WebSocketUpgrade}, State};
|
|||||||
use jsonwebtoken::{DecodingKey, EncodingKey};
|
use jsonwebtoken::{DecodingKey, EncodingKey};
|
||||||
use reqwest::header::AUTHORIZATION;
|
use reqwest::header::AUTHORIZATION;
|
||||||
use reqwest::header::CONTENT_TYPE;
|
use reqwest::header::CONTENT_TYPE;
|
||||||
|
use reqwest::header::USER_AGENT;
|
||||||
use tokio::net::TcpListener;
|
use tokio::net::TcpListener;
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
|
|
||||||
@@ -100,7 +101,7 @@ async fn main() -> std::io::Result<()> {
|
|||||||
let cors = CorsLayer::very_permissive()
|
let cors = CorsLayer::very_permissive()
|
||||||
.allow_credentials(true)
|
.allow_credentials(true)
|
||||||
.allow_methods([Method::GET, Method::POST, Method::PUT, Method::OPTIONS])
|
.allow_methods([Method::GET, Method::POST, Method::PUT, Method::OPTIONS])
|
||||||
.allow_headers([CONTENT_TYPE, AUTHORIZATION]);
|
.allow_headers([CONTENT_TYPE, AUTHORIZATION, USER_AGENT]);
|
||||||
let app = create_router(state)
|
let app = create_router(state)
|
||||||
.layer(Extension(jwt_keys))
|
.layer(Extension(jwt_keys))
|
||||||
.layer(cors);
|
.layer(cors);
|
||||||
|
|||||||
@@ -406,7 +406,7 @@ pub async fn clean_auth_loging(
|
|||||||
};
|
};
|
||||||
|
|
||||||
if !verify_password(&payload.password, &stored_hash) {
|
if !verify_password(&payload.password, &stored_hash) {
|
||||||
return (StatusCode::UNAUTHORIZED, "Invelid credentials").into_response();
|
return (StatusCode::UNAUTHORIZED, "Invalid credentials").into_response();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -472,10 +472,14 @@ pub async fn create_refresh_token(
|
|||||||
OsRng.fill_bytes(&mut bytes);
|
OsRng.fill_bytes(&mut bytes);
|
||||||
let raw_token = Uuid::new_v4().to_string();
|
let raw_token = Uuid::new_v4().to_string();
|
||||||
|
|
||||||
|
|
||||||
|
let hashed_token = &raw_token;
|
||||||
|
/*
|
||||||
let hashed_token = argon2
|
let hashed_token = argon2
|
||||||
.hash_password(raw_token.as_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 mut stmt = conn.prepare(
|
// let mut stmt = conn.prepare(
|
||||||
// "SELECT id, password FROM users WHERE username = ?1"
|
// "SELECT id, password FROM users WHERE username = ?1"
|
||||||
@@ -534,17 +538,19 @@ pub async fn create_refresh_token(
|
|||||||
/*.map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "Error mapping hotel_ids".to_string())); */
|
/*.map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "Error mapping hotel_ids".to_string())); */
|
||||||
|
|
||||||
//FIXME: might not need the hotel list on tconflict ?
|
//FIXME: might not need the hotel list on tconflict ?
|
||||||
|
|
||||||
|
//TODO: remove user agent entirely from auth ,it is mutable and not stable
|
||||||
|
//TODO: make the token refresh on login
|
||||||
conn.execute(
|
conn.execute(
|
||||||
r#"
|
r#"
|
||||||
INSERT INTO refresh_token (
|
INSERT INTO refresh_token (
|
||||||
user_id,
|
user_id,
|
||||||
token_hash,
|
token_hash,
|
||||||
device_id,
|
device_id,
|
||||||
user_agent,
|
|
||||||
hotel_id_list
|
hotel_id_list
|
||||||
)
|
)
|
||||||
VALUES (?1, ?2, ?3, ?4, ?5)
|
VALUES (?1, ?2, ?3, ?4)
|
||||||
ON CONFLICT(user_id, device_id, user_agent)
|
ON CONFLICT(user_id, device_id)
|
||||||
DO UPDATE SET
|
DO UPDATE SET
|
||||||
token_hash = excluded.token_hash,
|
token_hash = excluded.token_hash,
|
||||||
hotel_id_list = excluded.hotel_id_list
|
hotel_id_list = excluded.hotel_id_list
|
||||||
@@ -553,7 +559,6 @@ pub async fn create_refresh_token(
|
|||||||
user_id,
|
user_id,
|
||||||
hashed_token,
|
hashed_token,
|
||||||
device_id_str,
|
device_id_str,
|
||||||
user_agent_str,
|
|
||||||
hotel_ids_json
|
hotel_ids_json
|
||||||
],
|
],
|
||||||
).map_err(|e| {
|
).map_err(|e| {
|
||||||
@@ -562,6 +567,10 @@ pub async fn create_refresh_token(
|
|||||||
|
|
||||||
//TODO: add a map/tupple of of the allowed hotels and their id+name, maybe update the token ?
|
//TODO: add a map/tupple of of the allowed hotels and their id+name, maybe update the token ?
|
||||||
|
|
||||||
|
println!("RAW write refresh_token bytes: {:?}", &raw_token.as_bytes());
|
||||||
|
println!("RAW refresh_token : {}", &raw_token.to_string());
|
||||||
|
println!("RAW write refresh_token len: {}", &raw_token.len());
|
||||||
|
|
||||||
let cookie_value = format!("refresh_token={}; HttpOnly; Secure; Max-Age=60480000000;Path=/", raw_token);
|
let cookie_value = format!("refresh_token={}; HttpOnly; Secure; Max-Age=60480000000;Path=/", raw_token);
|
||||||
|
|
||||||
let mut response = (StatusCode::CREATED, "Refresh token created successfully").into_response();
|
let mut response = (StatusCode::CREATED, "Refresh token created successfully").into_response();
|
||||||
@@ -602,6 +611,10 @@ pub async fn login_refresh_token (
|
|||||||
None => return (StatusCode::UNAUTHORIZED, "Missing refresh token cookie").into_response(),
|
None => return (StatusCode::UNAUTHORIZED, "Missing refresh token cookie").into_response(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
println!("RAW refresh_token bytes: {:?}", refresh_token.as_bytes());
|
||||||
|
println!("RAW refresh_token : {}", refresh_token.to_string());
|
||||||
|
println!("RAW refresh_token len: {}", refresh_token.len());
|
||||||
|
|
||||||
println!("Cookies: {:?}", &refresh_token);
|
println!("Cookies: {:?}", &refresh_token);
|
||||||
|
|
||||||
let conn = match state.logs_pool.get() {
|
let conn = match state.logs_pool.get() {
|
||||||
@@ -624,22 +637,22 @@ pub async fn login_refresh_token (
|
|||||||
//deserializing the list :
|
//deserializing the list :
|
||||||
//let hotel_ids: Vec<i32> = serde_json::from_str(&stored_value)?;
|
//let hotel_ids: Vec<i32> = serde_json::from_str(&stored_value)?;
|
||||||
let mut stmt = match conn.prepare(
|
let mut stmt = match conn.prepare(
|
||||||
"SELECT user_id, token_hash, hotel_id_list
|
"SELECT user_id, hotel_id_list
|
||||||
FROM refresh_token
|
FROM refresh_token
|
||||||
WHERE device_id = ?1 AND user_agent = ?2
|
WHERE device_id = ?1 AND token_hash = ?2
|
||||||
LIMIT 1;"
|
LIMIT 1;"
|
||||||
) {
|
) {
|
||||||
Ok(s) => s,
|
Ok(s) => s,
|
||||||
Err(_) => return (StatusCode::INTERNAL_SERVER_ERROR, "Error prepatring hotel_id_list stmt").into_response(),
|
Err(_) => return (StatusCode::INTERNAL_SERVER_ERROR, "Error prepatring hotel_id_list stmt").into_response(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let rows = match stmt.query_one(params![&device_id_str, &user_agent_str], |row| {
|
let rows = match stmt.query_one(params![&device_id_str, &refresh_token], |row| {
|
||||||
Ok((
|
Ok((
|
||||||
row.get::<_, i32>(0)?, // user_id
|
row.get::<_, i32>(0)?, // user_id
|
||||||
row.get::<_, String>(1)?, // token_hash
|
row.get::<_, String>(1)?, // token_hash
|
||||||
row.get::<_, String>(2)?, // hotel_id //FIXME: this is supposed to be vectore maybe ?
|
//row.get::<_, String>(2)?, // hotel_id //FIXME: this is supposed to be vectore maybe ?
|
||||||
))
|
))
|
||||||
}) {
|
}).optional() {
|
||||||
Ok(r) => r,
|
Ok(r) => r,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!("DB ERROR: {:?}", e);
|
eprintln!("DB ERROR: {:?}", e);
|
||||||
@@ -648,8 +661,16 @@ pub async fn login_refresh_token (
|
|||||||
};
|
};
|
||||||
//TODO: extraction of the blob
|
//TODO: extraction of the blob
|
||||||
//let json_hotel_ids = rows.2;
|
//let json_hotel_ids = rows.2;
|
||||||
let (user_id, saved_hash,json_hotel_ids) = rows;
|
let (user_id, json_hotel_ids) = match rows {
|
||||||
|
Some(r) => r,
|
||||||
|
None => {
|
||||||
|
return (
|
||||||
|
StatusCode::UNAUTHORIZED,
|
||||||
|
"No refresh token found for this device",
|
||||||
|
)
|
||||||
|
.into_response();
|
||||||
|
}
|
||||||
|
};
|
||||||
let hotel_ids: Vec<i32> = match serde_json::from_str(&json_hotel_ids) {
|
let hotel_ids: Vec<i32> = match serde_json::from_str(&json_hotel_ids) {
|
||||||
Ok(ids) => ids,
|
Ok(ids) => ids,
|
||||||
Err(_) => return (StatusCode::INTERNAL_SERVER_ERROR, "Hotel ids are not deserializable to Vec").into_response(),
|
Err(_) => return (StatusCode::INTERNAL_SERVER_ERROR, "Hotel ids are not deserializable to Vec").into_response(),
|
||||||
@@ -661,11 +682,18 @@ pub async fn login_refresh_token (
|
|||||||
return (StatusCode::UNAUTHORIZED, "No matching device").into_response();
|
return (StatusCode::UNAUTHORIZED, "No matching device").into_response();
|
||||||
}
|
}
|
||||||
|
|
||||||
if !verify_password(&refresh_token, &saved_hash) {
|
/*
|
||||||
|
|
||||||
|
eprintln!("DB ERROR: {:?}", &refresh_token);
|
||||||
|
eprintln!("DB ERROR: {:?}", &token);
|
||||||
|
|
||||||
|
|
||||||
|
//still not auto adding hotel user link when creating account
|
||||||
|
if (&refresh_token != &token) {
|
||||||
// skip rows with wrong hash
|
// skip rows with wrong hash
|
||||||
return (StatusCode::UNAUTHORIZED, "Invelid credentials").into_response();
|
return (StatusCode::UNAUTHORIZED, "Invelid credentials").into_response();
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
let expiration = match chrono::Utc::now().checked_add_signed(chrono::Duration::hours(15)) {
|
let expiration = match chrono::Utc::now().checked_add_signed(chrono::Duration::hours(15)) {
|
||||||
Some(time) => time.timestamp() as usize,
|
Some(time) => time.timestamp() as usize,
|
||||||
None => {
|
None => {
|
||||||
@@ -705,13 +733,25 @@ pub async fn logout_from_single_device (
|
|||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
Extension(keys): Extension<JwtKeys>,
|
Extension(keys): Extension<JwtKeys>,
|
||||||
user_agent: Option<TypedHeader<UserAgent>>,
|
user_agent: Option<TypedHeader<UserAgent>>,
|
||||||
|
cookie_header: Option<TypedHeader<headers::Cookie>>,
|
||||||
Json(payload): Json<LoginRefreshTokenValues>
|
Json(payload): Json<LoginRefreshTokenValues>
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
|
|
||||||
let user_agent_str = user_agent
|
let user_agent_str = user_agent
|
||||||
.map(|ua| ua.to_string())
|
.map(|TypedHeader(ua)| ua.as_str().to_owned())
|
||||||
.unwrap_or_else(|| "Unknown".to_string());
|
.unwrap_or_else(|| "Unknown".to_string());
|
||||||
|
|
||||||
|
let cookies = match cookie_header {
|
||||||
|
Some(token) => token,
|
||||||
|
None => return (StatusCode::UNAUTHORIZED, "Missing refresh token cookie").into_response(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let refresh_token = match cookies.get("refresh_token") {
|
||||||
|
Some(token) => token.to_string(),
|
||||||
|
None => return (StatusCode::UNAUTHORIZED, "Missing refresh token cookie").into_response(),
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
let device_id_str = payload.device_id.to_string();
|
let device_id_str = payload.device_id.to_string();
|
||||||
|
|
||||||
let conn = match state.logs_pool.get() {
|
let conn = match state.logs_pool.get() {
|
||||||
@@ -719,25 +759,25 @@ pub async fn logout_from_single_device (
|
|||||||
Err(_) => return (StatusCode::INTERNAL_SERVER_ERROR, "DB connection error").into_response(),
|
Err(_) => return (StatusCode::INTERNAL_SERVER_ERROR, "DB connection error").into_response(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
let device_row = match conn.query_row(
|
let device_row = match conn.query_row(
|
||||||
"SELECT user_id, token_hash, hotel_id_list, id FROM refresh_token WHERE device_id = ?1 AND user_agent = ?2 AND revoked = 0 ",
|
"SELECT user_id, hotel_id_list, id
|
||||||
params![&device_id_str, &user_agent_str],
|
FROM refresh_token
|
||||||
|
WHERE token_hash = ?1 AND revoked = 0 ",
|
||||||
|
params![&refresh_token],
|
||||||
|row| {
|
|row| {
|
||||||
let user_id: i32 = row.get(0)?;
|
let user_id: i32 = row.get(0)?;
|
||||||
let token_hash: String = row.get(1)?;
|
let json_hotel_id_list: String = row.get(1)?;
|
||||||
let json_hotel_id_list: String = row.get(2)?;
|
let id:i32 = row.get(2)?;
|
||||||
let id:i32 = row.get(3)?;
|
|
||||||
//let displayname: String = row.get(3)?;
|
//let displayname: String = row.get(3)?;
|
||||||
Ok((user_id, token_hash, json_hotel_id_list ,id))
|
Ok((user_id,json_hotel_id_list ,id))
|
||||||
},
|
},
|
||||||
).optional() {
|
).optional() {
|
||||||
Ok(opt) => opt,
|
Ok(opt) => opt,
|
||||||
Err(e) => return (StatusCode::INTERNAL_SERVER_ERROR, format!("DB query error : {}", e )).into_response(),
|
Err(e) => return (StatusCode::INTERNAL_SERVER_ERROR, format!("DB query error : {}", e )).into_response(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let (user_id, json_hotel_id_list, token_id) = match device_row {
|
||||||
|
|
||||||
let (user_id, token_hash, json_hotel_id_list, token_id) = match device_row {
|
|
||||||
Some(tuple) => tuple,
|
Some(tuple) => tuple,
|
||||||
None => return (StatusCode::UNAUTHORIZED, "No matching device").into_response(),
|
None => return (StatusCode::UNAUTHORIZED, "No matching device").into_response(),
|
||||||
};
|
};
|
||||||
@@ -755,18 +795,23 @@ pub async fn logout_from_single_device (
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
|
||||||
let revoked: Result<String, rusqlite::Error> = conn.query_row(
|
let revoked: Result<String, rusqlite::Error> = conn.query_row(
|
||||||
"UPDATE refresh_token SET revoked = 1 WHERE id = ?1 RETURNING device_id",
|
"DELETE FROM refresh_token
|
||||||
|
WHERE id = ?1
|
||||||
|
RETURNING device_id",
|
||||||
params![&token_id],
|
params![&token_id],
|
||||||
|row| row.get(0),
|
|row| row.get(0),
|
||||||
);
|
);
|
||||||
|
|
||||||
*/
|
let revoked_id = match (revoked) {
|
||||||
|
Ok(r) => r,
|
||||||
|
Err(e) => return (StatusCode::INTERNAL_SERVER_ERROR, "Hotel ids are not deserializable to Vec").into_response()
|
||||||
|
};
|
||||||
|
|
||||||
let cookie_value = format!("refresh_token={}; HttpOnly; Secure; Max-Age=0;Path=/", "loggedout");
|
let cookie_value = format!("refresh_token={}; HttpOnly; Secure; Max-Age=0;Path=/", "loggedout");
|
||||||
|
|
||||||
let mut response = (StatusCode::CREATED, format!("Token deleted for device id {}", &device_id_str))
|
let mut response = (StatusCode::CREATED, format!("Token deleted for device id {}", &revoked_id))
|
||||||
.into_response();
|
.into_response();
|
||||||
|
|
||||||
response.headers_mut().insert(
|
response.headers_mut().insert(
|
||||||
|
|||||||
Reference in New Issue
Block a user