161 lines
5.0 KiB
Rust
161 lines
5.0 KiB
Rust
use axum::{Extension, Json, extract::State, http::StatusCode, response::IntoResponse};
|
|
use axum_extra::extract::cookie::CookieJar;
|
|
use serde_json::json;
|
|
|
|
use crate::{
|
|
auth::{AuthenticatedSession, cleared_session_cookie, extract_session_token, session_cookie},
|
|
error::ApiError,
|
|
rate_limit::ClientIdentityBucket,
|
|
service::{
|
|
ChangePasswordPayload, CompleteBootstrapPayload, LoginPayload,
|
|
UpdateCurrentWorkspacePayload, UpdateProfilePayload,
|
|
},
|
|
state::AppState,
|
|
};
|
|
|
|
pub async fn bootstrap_status(
|
|
State(state): State<AppState>,
|
|
) -> Result<Json<serde_json::Value>, ApiError> {
|
|
Ok(Json(serde_json::json!(
|
|
state.service.bootstrap_status().await?
|
|
)))
|
|
}
|
|
|
|
pub async fn complete_bootstrap(
|
|
State(state): State<AppState>,
|
|
jar: CookieJar,
|
|
Json(payload): Json<CompleteBootstrapPayload>,
|
|
) -> Result<impl IntoResponse, ApiError> {
|
|
let (session_data, session) = state.service.complete_bootstrap(payload).await?;
|
|
let cookie_value = format!(
|
|
"{}.{}",
|
|
session_data.session_id.as_str(),
|
|
session_data.value
|
|
);
|
|
let jar = jar.add(session_cookie(state.service.auth_settings(), &cookie_value));
|
|
|
|
Ok((jar, Json(serde_json::json!(session))))
|
|
}
|
|
|
|
pub async fn login(
|
|
State(state): State<AppState>,
|
|
jar: CookieJar,
|
|
client_bucket: Option<Extension<ClientIdentityBucket>>,
|
|
Json(payload): Json<LoginPayload>,
|
|
) -> Result<impl IntoResponse, ApiError> {
|
|
let client_bucket = client_bucket
|
|
.as_ref()
|
|
.map(|Extension(bucket)| bucket.0.as_str())
|
|
.unwrap_or("anonymous:/api/auth/login");
|
|
let (session_data, session) = state
|
|
.service
|
|
.login_with_client_bucket(payload, client_bucket)
|
|
.await?;
|
|
let cookie_value = format!(
|
|
"{}.{}",
|
|
session_data.session_id.as_str(),
|
|
session_data.value
|
|
);
|
|
let jar = jar.add(session_cookie(state.service.auth_settings(), &cookie_value));
|
|
|
|
Ok((jar, Json(serde_json::json!(session))))
|
|
}
|
|
|
|
pub async fn logout(
|
|
State(state): State<AppState>,
|
|
jar: CookieJar,
|
|
) -> Result<impl IntoResponse, ApiError> {
|
|
if let Some((session_id, session_value)) = extract_session_token(&jar) {
|
|
state.service.logout(&session_id, &session_value).await?;
|
|
}
|
|
|
|
let jar = jar.remove(cleared_session_cookie(state.service.auth_settings()));
|
|
Ok((jar, StatusCode::NO_CONTENT))
|
|
}
|
|
|
|
pub async fn get_session(
|
|
State(state): State<AppState>,
|
|
jar: CookieJar,
|
|
) -> Result<Json<serde_json::Value>, ApiError> {
|
|
let (session_id, session_value) = extract_session_token(&jar)
|
|
.ok_or_else(|| ApiError::unauthorized("authentication required"))?;
|
|
let session = state
|
|
.service
|
|
.session_response(&session_id, &session_value)
|
|
.await?
|
|
.ok_or_else(|| ApiError::unauthorized("session is invalid or expired"))?;
|
|
|
|
Ok(Json(serde_json::json!(session)))
|
|
}
|
|
|
|
pub async fn refresh_session_csrf(
|
|
State(state): State<AppState>,
|
|
jar: CookieJar,
|
|
) -> Result<Json<serde_json::Value>, ApiError> {
|
|
let (session_id, session_value) = extract_session_token(&jar)
|
|
.ok_or_else(|| ApiError::unauthorized("authentication required"))?;
|
|
state
|
|
.service
|
|
.get_session(&session_id, &session_value)
|
|
.await?
|
|
.ok_or_else(|| ApiError::unauthorized("session is invalid or expired"))?;
|
|
let csrf_token = state.service.rotate_session_csrf_token(&session_id).await?;
|
|
|
|
Ok(Json(json!({ "csrf_token": csrf_token })))
|
|
}
|
|
|
|
pub async fn get_profile(
|
|
Extension(session): Extension<AuthenticatedSession>,
|
|
) -> Result<Json<serde_json::Value>, ApiError> {
|
|
Ok(Json(json!({
|
|
"user": session.user,
|
|
"memberships": session.memberships,
|
|
"current_workspace_id": session.current_workspace_id
|
|
})))
|
|
}
|
|
|
|
pub async fn update_profile(
|
|
State(state): State<AppState>,
|
|
Extension(session): Extension<AuthenticatedSession>,
|
|
Json(payload): Json<UpdateProfilePayload>,
|
|
) -> Result<Json<serde_json::Value>, ApiError> {
|
|
let updated = state
|
|
.service
|
|
.update_profile(
|
|
&session.user.id,
|
|
&session.session_id,
|
|
session.current_workspace_id.as_ref(),
|
|
payload,
|
|
)
|
|
.await?;
|
|
Ok(Json(json!(updated)))
|
|
}
|
|
|
|
pub async fn update_current_workspace(
|
|
State(state): State<AppState>,
|
|
Extension(session): Extension<AuthenticatedSession>,
|
|
Json(payload): Json<UpdateCurrentWorkspacePayload>,
|
|
) -> Result<Json<serde_json::Value>, ApiError> {
|
|
let updated = state
|
|
.service
|
|
.set_current_workspace(
|
|
&session.session_id,
|
|
&session.user.id,
|
|
&payload.workspace_id.as_str().into(),
|
|
)
|
|
.await?;
|
|
Ok(Json(json!(updated)))
|
|
}
|
|
|
|
pub async fn change_password(
|
|
State(state): State<AppState>,
|
|
Extension(session): Extension<AuthenticatedSession>,
|
|
Json(payload): Json<ChangePasswordPayload>,
|
|
) -> Result<StatusCode, ApiError> {
|
|
state
|
|
.service
|
|
.change_password(&session.user.id, &session.session_id, payload)
|
|
.await?;
|
|
Ok(StatusCode::NO_CONTENT)
|
|
}
|