env and docker fixes
This commit is contained in:
15
.dockerignore
Normal file
15
.dockerignore
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# Rust build output
|
||||||
|
target/
|
||||||
|
|
||||||
|
# Git metadata
|
||||||
|
.git
|
||||||
|
.gitignore
|
||||||
|
|
||||||
|
# Local dev configs
|
||||||
|
.env
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# OS junk
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1 +1,3 @@
|
|||||||
/target
|
/target
|
||||||
|
|
||||||
|
.env
|
||||||
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -743,6 +743,7 @@ version = "0.35.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "133c182a6a2c87864fe97778797e46c7e999672690dc9fa3ee8e241aa4a9c13f"
|
checksum = "133c182a6a2c87864fe97778797e46c7e999672690dc9fa3ee8e241aa4a9c13f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"cc",
|
||||||
"pkg-config",
|
"pkg-config",
|
||||||
"vcpkg",
|
"vcpkg",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ bcrypt = "0.17"
|
|||||||
r2d2 = "0.8"
|
r2d2 = "0.8"
|
||||||
r2d2_sqlite = "0.31.0"
|
r2d2_sqlite = "0.31.0"
|
||||||
dashmap = "6.1"
|
dashmap = "6.1"
|
||||||
rusqlite = "0.37.0"
|
rusqlite = { version = "0.37", features = ["bundled"] }
|
||||||
jsonwebtoken = "9.3.1"
|
jsonwebtoken = "9.3.1"
|
||||||
dotenvy = "0.15.7"
|
dotenvy = "0.15.7"
|
||||||
chrono = "0.4.42"
|
chrono = "0.4.42"
|
||||||
|
|||||||
16
Dockerfile
Normal file
16
Dockerfile
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
FROM rust:latest AS builder
|
||||||
|
WORKDIR /app
|
||||||
|
COPY . .
|
||||||
|
RUN cargo build --release
|
||||||
|
|
||||||
|
FROM debian:bookworm-slim
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=builder /app/target/release/hotel-api-rs /usr/local/bin/hotel-api-rs
|
||||||
|
|
||||||
|
# Create the directory where DB will be stored
|
||||||
|
RUN mkdir -p /db
|
||||||
|
|
||||||
|
# Expose API port
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
CMD ["/usr/local/bin/hotel-api-rs"]
|
||||||
BIN
db/1.sqlite
BIN
db/1.sqlite
Binary file not shown.
BIN
db/auth.sqlite
BIN
db/auth.sqlite
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -278,4 +278,5 @@ pub async fn get_hotel_users(
|
|||||||
Ok(json) => (StatusCode::OK, json),
|
Ok(json) => (StatusCode::OK, json),
|
||||||
Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, format!("Serialization failed: {}", e)),
|
Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, format!("Serialization failed: {}", e)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
14
src/main.rs
14
src/main.rs
@@ -19,14 +19,15 @@ use crate::utils::db_pool::{HotelPool,AppState};
|
|||||||
use routes::create_router;
|
use routes::create_router;
|
||||||
use crate::utils::auth::JwtKeys;
|
use crate::utils::auth::JwtKeys;
|
||||||
|
|
||||||
|
use std::env;
|
||||||
|
use dotenvy::dotenv;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#[tokio::main(flavor = "multi_thread", worker_threads = 8)]
|
#[tokio::main(flavor = "multi_thread", worker_threads = 8)]
|
||||||
async fn main() -> std::io::Result<()> {
|
async fn main() -> std::io::Result<()> {
|
||||||
|
|
||||||
|
dotenv().ok();
|
||||||
|
|
||||||
let hotel_pools = HotelPool::new();
|
let hotel_pools = HotelPool::new();
|
||||||
let logs_manager = SqliteConnectionManager::file("db/auth.sqlite");
|
let logs_manager = SqliteConnectionManager::file("db/auth.sqlite");
|
||||||
@@ -42,7 +43,12 @@ async fn main() -> std::io::Result<()> {
|
|||||||
//jwt_secret: "your_jwt_secret_key s".to_string(), // better: load from env var
|
//jwt_secret: "your_jwt_secret_key s".to_string(), // better: load from env var
|
||||||
};
|
};
|
||||||
|
|
||||||
let jwt_secret = "your_jwt_secret_key".to_string();
|
//let jwt_secret = "your_jwt_secret_key".to_string();
|
||||||
|
|
||||||
|
let jwt_secret = env::var("JWT_SECRET")
|
||||||
|
.expect("JWT_SECRET must be set")
|
||||||
|
.to_string();
|
||||||
|
|
||||||
let jwt_keys = JwtKeys {
|
let jwt_keys = JwtKeys {
|
||||||
encoding: EncodingKey::from_secret(jwt_secret.as_ref()),
|
encoding: EncodingKey::from_secret(jwt_secret.as_ref()),
|
||||||
decoding: DecodingKey::from_secret(jwt_secret.as_ref()),
|
decoding: DecodingKey::from_secret(jwt_secret.as_ref()),
|
||||||
@@ -53,7 +59,7 @@ async fn main() -> std::io::Result<()> {
|
|||||||
let app = create_router(state)
|
let app = create_router(state)
|
||||||
.layer(Extension(jwt_keys));
|
.layer(Extension(jwt_keys));
|
||||||
|
|
||||||
let listener = TcpListener::bind("0.0.0.0:3000").await?;
|
let listener = TcpListener::bind("0.0.0.0:8080").await?;
|
||||||
serve(listener, app).into_future().await?;
|
serve(listener, app).into_future().await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use uuid::Uuid;
|
|
||||||
use chrono::{DateTime, Utc};
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, sqlx::FromRow)]
|
|
||||||
pub struct Room{
|
|
||||||
pub id: i32,
|
|
||||||
pub room_number: i32,
|
|
||||||
pub status: String
|
|
||||||
}
|
|
||||||
@@ -235,7 +235,7 @@ pub struct UpdatePasswordValues{
|
|||||||
username: String,
|
username: String,
|
||||||
current_password: String,
|
current_password: String,
|
||||||
newpassword: String,
|
newpassword: String,
|
||||||
hotel_id: i32,
|
//hotel_id: i32,
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -296,7 +296,7 @@ pub async fn UpdatePassword(
|
|||||||
pub struct LoginValues {
|
pub struct LoginValues {
|
||||||
username : String,
|
username : String,
|
||||||
password : String,
|
password : String,
|
||||||
hotel_id: i32,
|
//hotel_id: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct LoginPayload(pub LoginValues);
|
pub struct LoginPayload(pub LoginValues);
|
||||||
@@ -320,7 +320,7 @@ struct Claims{
|
|||||||
id: i32,
|
id: i32,
|
||||||
hotel_id: i32,
|
hotel_id: i32,
|
||||||
//display_name
|
//display_name
|
||||||
username: String,
|
//username: String,
|
||||||
exp: usize,
|
exp: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -376,7 +376,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": hotel_id,
|
"hotel_id": hotel_id,
|
||||||
"username": payload.username,
|
//"username": payload.username,
|
||||||
"exp": expiration
|
"exp": expiration
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -400,6 +400,7 @@ pub struct CreateRefreshTokenValue {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//TODO: refactor this to impl IntoResponse ans not Result
|
||||||
#[axum::debug_handler]
|
#[axum::debug_handler]
|
||||||
pub async fn create_refresh_token(
|
pub async fn create_refresh_token(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
@@ -417,7 +418,6 @@ pub async fn create_refresh_token(
|
|||||||
let salt = SaltString::generate(&mut OsRng);
|
let salt = SaltString::generate(&mut OsRng);
|
||||||
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 raw_token = Uuid::new_v4().to_string();
|
||||||
|
|
||||||
let hashed_token = argon2
|
let hashed_token = argon2
|
||||||
@@ -504,19 +504,16 @@ pub async fn login_refresh_token (
|
|||||||
Ok(opt) => opt,
|
Ok(opt) => opt,
|
||||||
Err(_) => return (StatusCode::INTERNAL_SERVER_ERROR, "DB query error").into_response(),
|
Err(_) => return (StatusCode::INTERNAL_SERVER_ERROR, "DB query error").into_response(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
let (user_id, token_hash, hotel_id) = match device_row {
|
let (user_id, token_hash, hotel_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(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
if !verify_password(&payload.refresh_token, &token_hash) {
|
if !verify_password(&payload.refresh_token, &token_hash) {
|
||||||
return (StatusCode::UNAUTHORIZED, "Invalid or mismatched token").into_response();
|
return (StatusCode::UNAUTHORIZED, "Invalid or mismatched token").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 => {
|
||||||
@@ -543,6 +540,98 @@ pub async fn login_refresh_token (
|
|||||||
Json(LoginResponse { token }).into_response()
|
Json(LoginResponse { token }).into_response()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn logout_from_single_device (
|
||||||
|
State(state): State<AppState>,
|
||||||
|
Extension(keys): Extension<JwtKeys>,
|
||||||
|
user_agent: Option<TypedHeader<UserAgent>>,
|
||||||
|
Json(payload): Json<LoginRefreshTokenValues>
|
||||||
|
) -> impl IntoResponse {
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
let conn = match state.logs_pool.get() {
|
||||||
|
Ok(c) => c,
|
||||||
|
Err(_) => return (StatusCode::INTERNAL_SERVER_ERROR, "DB connection error").into_response(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let device_row = match conn.query_row(
|
||||||
|
"SELECT user_id, token_hash, hotel_id, id FROM refresh_token WHERE device_id = ?1 AND user_agent = ?2 AND revoke = 0 ",
|
||||||
|
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 id:i32 = row.get(3)?;
|
||||||
|
//let displayname: String = row.get(3)?;
|
||||||
|
Ok((user_id, token_hash, hotel_id,id))
|
||||||
|
},
|
||||||
|
).optional() {
|
||||||
|
Ok(opt) => opt,
|
||||||
|
Err(_) => return (StatusCode::INTERNAL_SERVER_ERROR, "DB query error").into_response(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let (user_id, token_hash, hotel_id, token_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 revoked: Result<String, rusqlite::Error> = conn.query_row(
|
||||||
|
"UPDATE refresh_token SET revoked = 1 WHERE id = ?1 RETURNING device_id",
|
||||||
|
params![&token_id],
|
||||||
|
|row| row.get(0),
|
||||||
|
);
|
||||||
|
|
||||||
|
return (StatusCode::OK, format!("Token deleted for device id {}", &device_id_str)).into_response()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn logout_from_all_devices (
|
||||||
|
State(state): State<AppState>,
|
||||||
|
Extension(keys): Extension<JwtKeys>,
|
||||||
|
AuthClaims { user_id, hotel_id }: AuthClaims,
|
||||||
|
Json(payload): Json<LoginRefreshTokenValues>
|
||||||
|
) -> impl IntoResponse {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
let device_id_str = payload.device_id.to_string();
|
||||||
|
|
||||||
|
let conn = match state.logs_pool.get() {
|
||||||
|
Ok(c) => c,
|
||||||
|
Err(_) => return (StatusCode::INTERNAL_SERVER_ERROR, "DB connection error").into_response(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = conn.execute(
|
||||||
|
"UPDATE refresh_token SET revoked = 1 WHERE user_id = ?1 AND revoked = 0",
|
||||||
|
params![&user_id],
|
||||||
|
);
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(count) if count > 0 => {
|
||||||
|
(StatusCode::OK, format!("Revoked {} active tokens", count)).into_response()
|
||||||
|
}
|
||||||
|
Ok(_) => (StatusCode::NOT_FOUND, "No active tokens to revoke").into_response(),
|
||||||
|
Err(_) => (
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
"Database update error".to_string(),
|
||||||
|
)
|
||||||
|
.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))
|
||||||
}
|
}
|
||||||
@@ -23,5 +23,11 @@ pub fn utils_routes() -> Router<AppState> {
|
|||||||
|
|
||||||
.route("/create_refresh", post(create_refresh_token))
|
.route("/create_refresh", post(create_refresh_token))
|
||||||
.route("/login_refresh_token", post(login_refresh_token))
|
.route("/login_refresh_token", post(login_refresh_token))
|
||||||
|
|
||||||
|
.route("/logout_single_device", post(logout_from_single_device))
|
||||||
|
.route("/logout_all_devices", post(logout_from_all_devices))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//.with_state(state)
|
//.with_state(state)
|
||||||
}
|
}
|
||||||
9
test.rs
9
test.rs
@@ -1,9 +0,0 @@
|
|||||||
fn get_pool(hotel_id: String) -> connection {
|
|
||||||
|
|
||||||
if connection {
|
|
||||||
return connection;
|
|
||||||
}
|
|
||||||
|
|
||||||
create connection
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1 +1,6 @@
|
|||||||
cross build --release --target aarch64-unknown-linux-gnu
|
cross build --release --target aarch64-unknown-linux-gnu
|
||||||
|
|
||||||
|
docker run -p 8080:8080 \
|
||||||
|
-v ${PWD}/db:/db \
|
||||||
|
-e JWT_SECRET="my-dev-secret" \
|
||||||
|
rust-api:1.0.0
|
||||||
Reference in New Issue
Block a user