fix: break infinite redirect for non-admin users on admin UI
Root cause: auth_middleware redirected all non-admins (including logged-in ones) to /login, and login_page redirected logged-in users back — a loop. Fix: - auth_middleware now distinguishes unauthenticated (→ /login?next=) from logged-in-but-not-admin (→ /denied), breaking the loop entirely - /denied page's "sign in with a different account" link now goes to /logout first, so clicking it clears the session before the login form appears The login_page auto-redirect for logged-in users is restored, which is required for the Caddy forward_auth flow (deployed apps redirecting through /login?next=<app-url>). https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
This commit is contained in:
parent
812c81104a
commit
1671aaf8e8
1 changed files with 26 additions and 34 deletions
|
|
@ -96,28 +96,33 @@ pub async fn auth_middleware(
|
|||
|
||||
let user_id = current_user_id(&state, request.headers()).await;
|
||||
|
||||
let is_admin = match user_id {
|
||||
None => false,
|
||||
Some(uid) => {
|
||||
sqlx::query_scalar::<_, i64>("SELECT is_admin FROM users WHERE id = ?")
|
||||
.bind(&uid)
|
||||
.fetch_optional(&state.db)
|
||||
.await
|
||||
.unwrap_or(None)
|
||||
.map(|v| v != 0)
|
||||
.unwrap_or(false)
|
||||
}
|
||||
};
|
||||
|
||||
if is_admin {
|
||||
next.run(request).await
|
||||
} else {
|
||||
let path = request
|
||||
.uri()
|
||||
.path_and_query()
|
||||
.map(|p| p.as_str())
|
||||
.unwrap_or("/");
|
||||
Redirect::to(&format!("/login?next={}", safe_path(path))).into_response()
|
||||
|
||||
let uid = match user_id {
|
||||
None => {
|
||||
// Not logged in → send to login with return path.
|
||||
return Redirect::to(&format!("/login?next={}", safe_path(path))).into_response();
|
||||
}
|
||||
Some(uid) => uid,
|
||||
};
|
||||
|
||||
let is_admin = sqlx::query_scalar::<_, i64>("SELECT is_admin FROM users WHERE id = ?")
|
||||
.bind(&uid)
|
||||
.fetch_optional(&state.db)
|
||||
.await
|
||||
.unwrap_or(None)
|
||||
.map(|v| v != 0)
|
||||
.unwrap_or(false);
|
||||
|
||||
if is_admin {
|
||||
next.run(request).await
|
||||
} else {
|
||||
// Logged in but not an admin → access denied, no redirect loop.
|
||||
Redirect::to("/denied").into_response()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -243,24 +248,11 @@ pub async fn login_page(
|
|||
headers: HeaderMap,
|
||||
Query(params): Query<NextParam>,
|
||||
) -> Response {
|
||||
// Already logged in as an admin → redirect.
|
||||
if let Some(uid) = current_user_id(&state, &headers).await {
|
||||
let is_admin = if uid == "bootstrap" {
|
||||
true
|
||||
} else {
|
||||
sqlx::query_scalar::<_, i64>("SELECT is_admin FROM users WHERE id = ?")
|
||||
.bind(&uid)
|
||||
.fetch_optional(&state.db)
|
||||
.await
|
||||
.unwrap_or(None)
|
||||
.map(|v| v != 0)
|
||||
.unwrap_or(false)
|
||||
};
|
||||
if is_admin {
|
||||
// Already logged in → redirect.
|
||||
if current_user_id(&state, &headers).await.is_some() {
|
||||
let next = params.next.as_deref().map(|n| safe_redirect(n, &state.domain_suffix)).unwrap_or_else(|| "/".into());
|
||||
return Redirect::to(&next).into_response();
|
||||
}
|
||||
}
|
||||
let next = params.next.map(|s| safe_redirect(&s, &state.domain_suffix)).unwrap_or_else(|| "/".into());
|
||||
Html(login_html(&next, None)).into_response()
|
||||
}
|
||||
|
|
@ -361,7 +353,7 @@ a{{color:#818cf8}}</style></head><body>
|
|||
<h1>Access denied</h1>
|
||||
<p>You do not have access to <strong>{app}</strong>.</p>
|
||||
<p>Contact your administrator to request access.</p>
|
||||
<a href="/login">← Sign in with a different account</a>
|
||||
<a href="/logout">← Sign in with a different account</a>
|
||||
</div></body></html>"#
|
||||
))
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue