Skip to content

Commit 3e378ea

Browse files
committed
fix(backend): enforce imports page permissions
1 parent f3d5776 commit 3e378ea

File tree

4 files changed

+55
-9
lines changed

4 files changed

+55
-9
lines changed

rustytime/Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

rustytime/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[package]
22
name = "rustytime-server"
33
description = "🕒 blazingly fast time tracking for developers"
4-
version = "0.15.0"
4+
version = "0.15.1"
55
edition = "2024"
66
authors = ["ImShyMike"]
77
readme = "../README.md"

rustytime/src/routes.rs

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -207,13 +207,6 @@ pub fn create_app_router(
207207
.tag("Pages")
208208
.security_requirement("Authenticated")
209209
}))
210-
.api_route("/page/imports", get_with(admin_imports, |op| {
211-
op.id("admin_imports")
212-
.summary("Admin Imports Page")
213-
.description("Data for the admin imports page.")
214-
.tag("Pages")
215-
.security_requirement("Authenticated")
216-
}))
217210
.nest(
218211
"/admin",
219212
ApiRouter::new()
@@ -248,6 +241,21 @@ pub fn create_app_router(
248241
middleware::require_admin,
249242
)),
250243
)
244+
// owner routes
245+
.merge(
246+
ApiRouter::new()
247+
.api_route("/page/imports", get_with(admin_imports, |op| {
248+
op.id("admin_imports")
249+
.summary("Admin Imports Page")
250+
.description("Data for the admin imports page.")
251+
.tag("Pages")
252+
.security_requirement("Authenticated")
253+
}))
254+
.layer(axum_middleware::from_fn_with_state(
255+
app_state.clone(),
256+
middleware::require_owner,
257+
)),
258+
)
251259
// API routes
252260
.nest(
253261
"/api/v1",

rustytime/src/utils/middleware.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,44 @@ pub async fn require_admin(
8686
}
8787
}
8888

89+
/// Middleware to require owner privileges
90+
pub async fn require_owner(
91+
State(app_state): State<AppState>,
92+
cookies: Cookies,
93+
mut request: Request,
94+
next: Next,
95+
) -> Response {
96+
match SessionManager::resolve_session(&cookies, &app_state.db_pool).await {
97+
Ok(Some(resolved)) => {
98+
let is_owner = resolved.user.is_owner()
99+
|| resolved
100+
.impersonator
101+
.as_ref()
102+
.map(|admin| admin.is_owner())
103+
.unwrap_or(false);
104+
105+
if !is_owner {
106+
return (StatusCode::FORBIDDEN, "Owner access required").into_response();
107+
}
108+
109+
{
110+
let extensions = request.extensions_mut();
111+
extensions.insert(resolved.user.clone());
112+
if let Some(admin) = resolved.impersonator.clone() {
113+
extensions.insert(ImpersonationContext { admin });
114+
}
115+
}
116+
117+
next.run(request).await
118+
}
119+
Ok(None) => {
120+
// user is not authenticated
121+
(StatusCode::UNAUTHORIZED, "Authentication required").into_response()
122+
}
123+
Err(_) => (StatusCode::INTERNAL_SERVER_ERROR, "Internal server error").into_response(),
124+
}
125+
}
126+
89127
/// Middleware to track request metrics
90128
#[inline(always)]
91129
pub async fn track_metrics(

0 commit comments

Comments
 (0)