Fix compilation errors: Add missing dependencies, fix module visibility, and update Axum middleware types
Some checks failed
Build MIPS Binary / build (push) Failing after 3m27s
Some checks failed
Build MIPS Binary / build (push) Failing after 3m27s
This commit is contained in:
5
Cargo.lock
generated
5
Cargo.lock
generated
@@ -99,9 +99,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anyhow"
|
name = "anyhow"
|
||||||
version = "1.0.100"
|
version = "1.0.101"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
|
checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "arbitrary"
|
name = "arbitrary"
|
||||||
@@ -341,6 +341,7 @@ dependencies = [
|
|||||||
name = "backend"
|
name = "backend"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
"axum 0.8.8",
|
"axum 0.8.8",
|
||||||
"axum-extra",
|
"axum-extra",
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
|
|||||||
@@ -37,3 +37,4 @@ sqlx = { version = "0.8", features = ["runtime-tokio", "sqlite"] }
|
|||||||
bcrypt = "0.17.0"
|
bcrypt = "0.17.0"
|
||||||
axum-extra = { version = "0.9", features = ["cookie"] }
|
axum-extra = { version = "0.9", features = ["cookie"] }
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
|
anyhow = "1.0.101"
|
||||||
|
|||||||
108
backend/src/handlers/auth.rs
Normal file
108
backend/src/handlers/auth.rs
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
use crate::{db::Db, AppState};
|
||||||
|
use axum::{
|
||||||
|
extract::{State, Json},
|
||||||
|
http::StatusCode,
|
||||||
|
response::IntoResponse,
|
||||||
|
};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use utoipa::ToSchema;
|
||||||
|
use axum_extra::extract::cookie::{Cookie, CookieJar, SameSite};
|
||||||
|
use time::Duration;
|
||||||
|
|
||||||
|
#[derive(Deserialize, ToSchema)]
|
||||||
|
pub struct LoginRequest {
|
||||||
|
username: String,
|
||||||
|
password: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct UserResponse {
|
||||||
|
username: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[utoipa::path(
|
||||||
|
post,
|
||||||
|
path = "/api/auth/login",
|
||||||
|
request_body = LoginRequest,
|
||||||
|
responses(
|
||||||
|
(status = 200, description = "Login successful"),
|
||||||
|
(status = 401, description = "Invalid credentials"),
|
||||||
|
(status = 500, description = "Internal server error")
|
||||||
|
)
|
||||||
|
)]
|
||||||
|
pub async fn login_handler(
|
||||||
|
State(state): State<AppState>,
|
||||||
|
jar: CookieJar,
|
||||||
|
Json(payload): Json<LoginRequest>,
|
||||||
|
) -> impl IntoResponse {
|
||||||
|
let user = match state.db.get_user_by_username(&payload.username).await {
|
||||||
|
Ok(Some(u)) => u,
|
||||||
|
Ok(None) => return (StatusCode::UNAUTHORIZED, "Invalid credentials").into_response(),
|
||||||
|
Err(e) => {
|
||||||
|
tracing::error!("DB error during login: {}", e);
|
||||||
|
return (StatusCode::INTERNAL_SERVER_ERROR, "Database error").into_response();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let (user_id, password_hash) = user;
|
||||||
|
|
||||||
|
match bcrypt::verify(&payload.password, &password_hash) {
|
||||||
|
Ok(true) => {
|
||||||
|
// Create session
|
||||||
|
let token: String = (0..32).map(|_| {
|
||||||
|
use rand::{distributions::Alphanumeric, Rng};
|
||||||
|
rand::thread_rng().sample(Alphanumeric) as char
|
||||||
|
}).collect();
|
||||||
|
|
||||||
|
// Expires in 30 days
|
||||||
|
let expires_in = 60 * 60 * 24 * 30;
|
||||||
|
let expires_at = time::OffsetDateTime::now_utc().unix_timestamp() + expires_in;
|
||||||
|
|
||||||
|
if let Err(e) = state.db.create_session(user_id, &token, expires_at).await {
|
||||||
|
tracing::error!("Failed to create session: {}", e);
|
||||||
|
return (StatusCode::INTERNAL_SERVER_ERROR, "Failed to create session").into_response();
|
||||||
|
}
|
||||||
|
|
||||||
|
let cookie = Cookie::build(("auth_token", token))
|
||||||
|
.path("/")
|
||||||
|
.http_only(true)
|
||||||
|
.same_site(SameSite::Strict)
|
||||||
|
.max_age(Duration::seconds(expires_in))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
(StatusCode::OK, jar.add(cookie), "Login successful").into_response()
|
||||||
|
}
|
||||||
|
_ => (StatusCode::UNAUTHORIZED, "Invalid credentials").into_response(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn logout_handler(
|
||||||
|
State(state): State<AppState>,
|
||||||
|
jar: CookieJar,
|
||||||
|
) -> impl IntoResponse {
|
||||||
|
if let Some(token) = jar.get("auth_token") {
|
||||||
|
let _ = state.db.delete_session(token.value()).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
let cookie = Cookie::build(("auth_token", ""))
|
||||||
|
.path("/")
|
||||||
|
.http_only(true)
|
||||||
|
.max_age(Duration::seconds(-1)) // Expire immediately
|
||||||
|
.build();
|
||||||
|
|
||||||
|
(StatusCode::OK, jar.add(cookie), "Logged out").into_response()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn check_auth_handler(
|
||||||
|
State(state): State<AppState>,
|
||||||
|
jar: CookieJar,
|
||||||
|
) -> impl IntoResponse {
|
||||||
|
if let Some(token) = jar.get("auth_token") {
|
||||||
|
match state.db.get_session_user(token.value()).await {
|
||||||
|
Ok(Some(_)) => return StatusCode::OK.into_response(),
|
||||||
|
_ => {} // Invalid session
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StatusCode::UNAUTHORIZED.into_response()
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
use crate::{db::Db, AppState};
|
use crate::AppState;
|
||||||
use axum::{
|
use axum::{
|
||||||
extract::{State, Json},
|
extract::{State, Json},
|
||||||
http::StatusCode,
|
http::StatusCode,
|
||||||
@@ -18,6 +18,13 @@ pub struct SetupStatusResponse {
|
|||||||
completed: bool,
|
completed: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[utoipa::path(
|
||||||
|
get,
|
||||||
|
path = "/api/setup/status",
|
||||||
|
responses(
|
||||||
|
(status = 200, description = "Setup status", body = SetupStatusResponse)
|
||||||
|
)
|
||||||
|
)]
|
||||||
pub async fn get_setup_status_handler(State(state): State<AppState>) -> impl IntoResponse {
|
pub async fn get_setup_status_handler(State(state): State<AppState>) -> impl IntoResponse {
|
||||||
let completed = match state.db.has_users().await {
|
let completed = match state.db.has_users().await {
|
||||||
Ok(has) => has,
|
Ok(has) => has,
|
||||||
@@ -29,6 +36,17 @@ pub async fn get_setup_status_handler(State(state): State<AppState>) -> impl Int
|
|||||||
Json(SetupStatusResponse { completed }).into_response()
|
Json(SetupStatusResponse { completed }).into_response()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[utoipa::path(
|
||||||
|
post,
|
||||||
|
path = "/api/setup",
|
||||||
|
request_body = SetupRequest,
|
||||||
|
responses(
|
||||||
|
(status = 200, description = "Setup completed"),
|
||||||
|
(status = 400, description = "Invalid request"),
|
||||||
|
(status = 403, description = "Setup already completed"),
|
||||||
|
(status = 500, description = "Internal server error")
|
||||||
|
)
|
||||||
|
)]
|
||||||
pub async fn setup_handler(
|
pub async fn setup_handler(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
Json(payload): Json<SetupRequest>,
|
Json(payload): Json<SetupRequest>,
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ use axum::{
|
|||||||
extract::Request,
|
extract::Request,
|
||||||
response::Response,
|
response::Response,
|
||||||
http::StatusCode,
|
http::StatusCode,
|
||||||
|
body::Body,
|
||||||
};
|
};
|
||||||
use axum_extra::extract::cookie::CookieJar;
|
use axum_extra::extract::cookie::CookieJar;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
@@ -46,7 +47,7 @@ pub struct AppState {
|
|||||||
async fn auth_middleware(
|
async fn auth_middleware(
|
||||||
state: axum::extract::State<AppState>,
|
state: axum::extract::State<AppState>,
|
||||||
jar: CookieJar,
|
jar: CookieJar,
|
||||||
request: Request,
|
request: Request<Body>,
|
||||||
next: Next,
|
next: Next,
|
||||||
) -> Result<Response, StatusCode> {
|
) -> Result<Response, StatusCode> {
|
||||||
// Skip auth for public paths
|
// Skip auth for public paths
|
||||||
@@ -110,6 +111,8 @@ struct Args {
|
|||||||
handlers::get_push_public_key_handler,
|
handlers::get_push_public_key_handler,
|
||||||
handlers::subscribe_push_handler,
|
handlers::subscribe_push_handler,
|
||||||
handlers::auth::login_handler,
|
handlers::auth::login_handler,
|
||||||
|
handlers::auth::logout_handler,
|
||||||
|
handlers::auth::check_auth_handler,
|
||||||
handlers::setup::setup_handler,
|
handlers::setup::setup_handler,
|
||||||
handlers::setup::get_setup_status_handler
|
handlers::setup::get_setup_status_handler
|
||||||
),
|
),
|
||||||
@@ -128,7 +131,8 @@ struct Args {
|
|||||||
push::PushSubscription,
|
push::PushSubscription,
|
||||||
push::PushKeys,
|
push::PushKeys,
|
||||||
handlers::auth::LoginRequest,
|
handlers::auth::LoginRequest,
|
||||||
handlers::setup::SetupRequest
|
handlers::setup::SetupRequest,
|
||||||
|
handlers::setup::SetupStatusResponse
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
tags(
|
tags(
|
||||||
@@ -152,6 +156,8 @@ struct ApiDoc;
|
|||||||
handlers::get_global_limit_handler,
|
handlers::get_global_limit_handler,
|
||||||
handlers::set_global_limit_handler,
|
handlers::set_global_limit_handler,
|
||||||
handlers::auth::login_handler,
|
handlers::auth::login_handler,
|
||||||
|
handlers::auth::logout_handler,
|
||||||
|
handlers::auth::check_auth_handler,
|
||||||
handlers::setup::setup_handler,
|
handlers::setup::setup_handler,
|
||||||
handlers::setup::get_setup_status_handler
|
handlers::setup::get_setup_status_handler
|
||||||
),
|
),
|
||||||
@@ -168,7 +174,8 @@ struct ApiDoc;
|
|||||||
shared::SetLabelRequest,
|
shared::SetLabelRequest,
|
||||||
shared::GlobalLimitRequest,
|
shared::GlobalLimitRequest,
|
||||||
handlers::auth::LoginRequest,
|
handlers::auth::LoginRequest,
|
||||||
handlers::setup::SetupRequest
|
handlers::setup::SetupRequest,
|
||||||
|
handlers::setup::SetupStatusResponse
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
tags(
|
tags(
|
||||||
@@ -210,7 +217,7 @@ async fn main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let db = match db::Db::new(&args.db_url).await {
|
let db: db::Db = match db::Db::new(&args.db_url).await {
|
||||||
Ok(db) => db,
|
Ok(db) => db,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
tracing::error!("Failed to connect to database: {}", e);
|
tracing::error!("Failed to connect to database: {}", e);
|
||||||
|
|||||||
Reference in New Issue
Block a user