This commit is contained in:
Mateusz Gruszczyński committed 2026-10-26 15:47:16 +01:00
1 parent f508b7c882
commit 25d04daaee
12 files changed
+323 -90

No files matched your search

+79 -18
View File
@@ -374,6 +374,11 @@ pub struct SessionResponse {
language: String,
}
#[derive(Serialize)]
struct SessionStatusResponse {
authenticated: bool,
session: Option<SessionResponse>,
}
#[derive(Serialize)]
pub struct IdentityResponse {
nickname: String,
registered: bool,
@@ -808,6 +813,30 @@ fn suggested_directory_nickname(display_name: Option<&str>, email: &str) -> Opti
.and_then(|value| validate_nickname(&value).ok())
}
/// Check whether a visitor has an active account session without treating guests as errors.
pub async fn session(
State(state): State<SharedState>,
headers: HeaderMap,
) -> Result<Response, AuthError> {
let Some(token) = crate::security::session_token(&headers) else {
return Ok(session_status_response(None, state.user_session_ttl_days));
};
let Some(user) = user_from_token(&state, token).await? else {
let mut response = session_status_response(None, state.user_session_ttl_days);
response
.headers_mut()
.insert(header::SET_COOKIE, crate::security::clear_session_cookie());
return Ok(response);
};
debug!(user_id = user.id, "session validation successful");
let details = session_details(&state, token, user).await?;
Ok(session_status_response(
Some(details),
state.user_session_ttl_days,
))
}
/// Preserve the existing protected `/api/auth/me` API contract (401 for guests).
pub async fn me(
State(state): State<SharedState>,
headers: HeaderMap,
@@ -822,6 +851,19 @@ pub async fn me(
return Ok(response);
};
debug!(user_id = user.id, "session validation successful");
let details = session_details(&state, token, user).await?;
Ok(session_json_response(
StatusCode::OK,
details,
state.user_session_ttl_days,
))
}
async fn session_details(
state: &SharedState,
token: &str,
user: User,
) -> Result<SessionResponse, AuthError> {
let expires_at: String = sqlx::query_scalar(queries::get(
state.db.kind(),
queries::AUTH_SESSION_EXPIRES_AT,
@@ -831,7 +873,7 @@ pub async fn me(
.await
.map_err(AuthError::database)?;
let (directory_managed, directory_display_name, directory_organization, suggested_nickname) =
directory_profile_metadata(&state, &user).await?;
directory_profile_metadata(state, &user).await?;
let editor_color: Option<String> = sqlx::query_scalar(queries::get(
state.db.kind(),
queries::AUTH_EDITOR_COLOR_BY_USER,
@@ -840,23 +882,19 @@ pub async fn me(
.fetch_one(state.db.pool())
.await
.map_err(AuthError::database)?;
Ok(session_json_response(
StatusCode::OK,
SessionResponse {
token: token.into(),
nickname: user.nickname,
email: user.email,
expires_at,
directory_managed,
directory_display_name,
directory_organization,
suggested_nickname,
editor_color,
theme: user.theme,
language: user.language,
},
state.user_session_ttl_days,
))
Ok(SessionResponse {
token: token.into(),
nickname: user.nickname,
email: user.email,
expires_at,
directory_managed,
directory_display_name,
directory_organization,
suggested_nickname,
editor_color,
theme: user.theme,
language: user.language,
})
}
pub async fn update_profile(
@@ -3135,6 +3173,29 @@ fn session_json_response(status: StatusCode, session: SessionResponse, ttl_days:
let cookie = crate::security::session_cookie(&session.token, ttl_days);
let mut response = (status, Json(session)).into_response();
response.headers_mut().insert(header::SET_COOKIE, cookie);
response.headers_mut().insert(
header::CACHE_CONTROL,
axum::http::HeaderValue::from_static("no-store"),
);
response
}
fn session_status_response(session: Option<SessionResponse>, ttl_days: i64) -> Response {
let cookie = session
.as_ref()
.map(|details| crate::security::session_cookie(&details.token, ttl_days));
let mut response = Json(SessionStatusResponse {
authenticated: session.is_some(),
session,
})
.into_response();
response.headers_mut().insert(
header::CACHE_CONTROL,
axum::http::HeaderValue::from_static("no-store"),
);
if let Some(cookie) = cookie {
response.headers_mut().insert(header::SET_COOKIE, cookie);
}
response
}