Skip to content

Commit d776b2e

Browse files
committed
refactor(api): add api authentification to auth middleware and implement a permission middleware to check api permissions
1 parent 5f467ba commit d776b2e

5 files changed

Lines changed: 110 additions & 7 deletions

File tree

Cargo.lock

Lines changed: 29 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

rustmail/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ serde_json = "1.0.145"
2525
rand = "0.9.2"
2626
base64 = "0.22.1"
2727
subtle = "2.6.1"
28+
prefixed-api-key = "0.3.0"
29+
sha2 = "0.10.8"
30+
hex = "0.4.3"
2831

2932
[dependencies.uuid]
3033
version = "1.18.1"

rustmail/src/api/middleware/auth.rs

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::db::operations::{get_api_key_by_hash, hash_api_key, update_last_used};
12
use crate::prelude::api::*;
23
use crate::prelude::types::*;
34
use axum::extract::State;
@@ -86,7 +87,7 @@ pub async fn auth_middleware(
8687
State(bot_state): State<Arc<Mutex<BotState>>>,
8788
ConnectInfo(addr): ConnectInfo<SocketAddr>,
8889
jar: CookieJar,
89-
req: Request,
90+
mut req: Request,
9091
next: Next,
9192
) -> Response {
9293
if addr.ip().is_loopback() {
@@ -103,12 +104,6 @@ pub async fn auth_middleware(
103104
}
104105
}
105106

106-
let session_cookie = jar.get("session_id");
107-
108-
if session_cookie.is_none() {
109-
return (StatusCode::UNAUTHORIZED, "Unauthorized").into_response();
110-
}
111-
112107
let db_pool = {
113108
let state_lock = bot_state.lock().await;
114109
match &state_lock.db_pool {
@@ -123,6 +118,44 @@ pub async fn auth_middleware(
123118
}
124119
};
125120

121+
if let Some(api_key_header) = req.headers().get("x-api-key") {
122+
if let Ok(api_key_str) = api_key_header.to_str() {
123+
let key_hash = hash_api_key(api_key_str);
124+
125+
match get_api_key_by_hash(&db_pool, &key_hash).await {
126+
Ok(Some(api_key)) => {
127+
if api_key.is_valid() {
128+
let pool_clone = db_pool.clone();
129+
let key_id = api_key.id;
130+
tokio::spawn(async move {
131+
let _ = update_last_used(&pool_clone, key_id).await;
132+
});
133+
134+
req.extensions_mut().insert(api_key);
135+
return next.run(req).await;
136+
} else {
137+
return (StatusCode::UNAUTHORIZED, "API key expired or inactive")
138+
.into_response();
139+
}
140+
}
141+
Ok(None) => {
142+
return (StatusCode::UNAUTHORIZED, "Invalid API key").into_response();
143+
}
144+
Err(e) => {
145+
eprintln!("Error fetching API key: {}", e);
146+
return (StatusCode::INTERNAL_SERVER_ERROR, "Internal server error")
147+
.into_response();
148+
}
149+
}
150+
}
151+
}
152+
153+
let session_cookie = jar.get("session_id");
154+
155+
if session_cookie.is_none() {
156+
return (StatusCode::UNAUTHORIZED, "Unauthorized").into_response();
157+
}
158+
126159
let session_id = session_cookie.unwrap().value().to_string();
127160
let user_id = get_user_id_from_session(&session_id, &db_pool).await;
128161

rustmail/src/api/middleware/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
pub mod auth;
2+
pub mod permissions;
23

34
pub use auth::*;
5+
pub use permissions::*;
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
use crate::db::repr::{ApiKey, Permission};
2+
use axum::http::StatusCode;
3+
use axum::response::{IntoResponse, Response};
4+
5+
#[derive(Debug)]
6+
pub struct PermissionError {
7+
pub required: &'static str,
8+
}
9+
10+
impl IntoResponse for PermissionError {
11+
fn into_response(self) -> Response {
12+
(
13+
StatusCode::FORBIDDEN,
14+
format!("Missing required permission: {}", self.required),
15+
)
16+
.into_response()
17+
}
18+
}
19+
20+
pub fn check_permission(api_key: &ApiKey, permission: Permission) -> Result<(), PermissionError> {
21+
if api_key.has_permission(permission) {
22+
Ok(())
23+
} else {
24+
Err(PermissionError {
25+
required: match permission {
26+
Permission::CreateTicket => "CREATE_TICKET",
27+
Permission::ReadTickets => "READ_TICKETS",
28+
Permission::UpdateTicket => "UPDATE_TICKET",
29+
Permission::DeleteTicket => "DELETE_TICKET",
30+
Permission::ReadConfig => "READ_CONFIG",
31+
Permission::UpdateConfig => "UPDATE_CONFIG",
32+
Permission::ManageBot => "MANAGE_BOT",
33+
},
34+
})
35+
}
36+
}

0 commit comments

Comments
 (0)