fixes
This commit is contained in:
+47
-2
@@ -19,6 +19,7 @@ pub struct PadInfo {
|
||||
title: String,
|
||||
protected: bool,
|
||||
allow_public_task_updates: bool,
|
||||
public_page_unprotected: bool,
|
||||
created_at: String,
|
||||
updated_at: String,
|
||||
can_delete_files: bool,
|
||||
@@ -82,6 +83,7 @@ pub async fn pad_info(
|
||||
title: pad.title,
|
||||
protected: pad.password_hash.is_some(),
|
||||
allow_public_task_updates: db::pad_public_task_updates(&state.db, pad.id).await?,
|
||||
public_page_unprotected: db::pad_public_page_unprotected(&state.db, pad.id).await?,
|
||||
created_at: db::normalize_timestamp(&pad.created_at),
|
||||
updated_at: db::normalize_timestamp(&pad.updated_at),
|
||||
can_delete_files: crate::auth::is_resource_owner(
|
||||
@@ -181,6 +183,7 @@ pub async fn publish_pad_page(
|
||||
require_write(level)?;
|
||||
let token = db::publish_pad(&state.db, pad.id).await?;
|
||||
db::set_pad_public_task_updates(&state.db, pad.id, payload.allow_task_updates).await?;
|
||||
db::set_pad_public_page_unprotected(&state.db, pad.id, payload.unprotect_page).await?;
|
||||
Ok(Json(PublishResponse {
|
||||
url: format!("/s/{token}"),
|
||||
}))
|
||||
@@ -218,18 +221,58 @@ pub async fn publish_note_page(
|
||||
require_write(level)?;
|
||||
let token = db::publish_note(&state.db, note.id).await?;
|
||||
db::set_note_public_task_updates(&state.db, note.id, payload.allow_task_updates).await?;
|
||||
db::set_note_public_page_unprotected(&state.db, note.id, payload.unprotect_page).await?;
|
||||
Ok(Json(PublishResponse {
|
||||
url: format!("/s/{token}"),
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
fn page_password(headers: &HeaderMap) -> Option<&str> {
|
||||
headers.get("x-rustpad-page-password").and_then(|value| value.to_str().ok()).map(str::trim).filter(|value| !value.is_empty())
|
||||
}
|
||||
|
||||
async fn ensure_public_page_access(state: &SharedState, headers: &HeaderMap, page: &db::PublishedPage) -> Result<(), ApiError> {
|
||||
let password = page_password(headers);
|
||||
let bearer = bearer_token(headers);
|
||||
if let Some(pad_id) = page.pad_id {
|
||||
if db::pad_public_page_unprotected(&state.db, pad_id).await? { return Ok(()); }
|
||||
let sql = match state.db.kind() {
|
||||
crate::database::DatabaseKind::Postgres => "SELECT slug FROM pads WHERE id = $1",
|
||||
_ => "SELECT slug FROM pads WHERE id = ?",
|
||||
};
|
||||
let slug: Option<String> = sqlx::query_scalar(sql).bind(pad_id).fetch_optional(state.db.pool()).await?;
|
||||
let Some(slug) = slug else { return Err(ApiError::not_found_note()); };
|
||||
let pad = db::find_pad(&state.db, &slug).await?.ok_or_else(ApiError::not_found_note)?;
|
||||
if db::verify_pad_password(&pad, password) { return Ok(()); }
|
||||
if verify_resource_access_token(state, "pad", &slug, bearer).await? { return Ok(()); }
|
||||
return if pad.password_hash.is_some() { Err(ApiError::forbidden("Password required or incorrect.")) } else { Err(ApiError::forbidden("This published page is protected.")) };
|
||||
}
|
||||
if let Some(note_id) = page.note_id {
|
||||
if db::note_public_page_unprotected(&state.db, note_id).await? { return Ok(()); }
|
||||
let sql = match state.db.kind() {
|
||||
crate::database::DatabaseKind::Postgres => "SELECT w.slug FROM notes n JOIN workspaces w ON w.id = n.workspace_id WHERE n.id = $1",
|
||||
_ => "SELECT w.slug FROM notes n JOIN workspaces w ON w.id = n.workspace_id WHERE n.id = ?",
|
||||
};
|
||||
let slug: Option<String> = sqlx::query_scalar(sql).bind(note_id).fetch_optional(state.db.pool()).await?;
|
||||
let Some(slug) = slug else { return Err(ApiError::not_found_note()); };
|
||||
let workspace = db::find_workspace(&state.db, &slug).await?.ok_or_else(ApiError::not_found_workspace)?;
|
||||
if db::verify_workspace_password(&workspace, password) { return Ok(()); }
|
||||
if verify_resource_access_token(state, "workspace", &slug, bearer).await? { return Ok(()); }
|
||||
return if workspace.password_hash.is_some() { Err(ApiError::forbidden("Password required or incorrect.")) } else { Err(ApiError::forbidden("This published page is protected.")) };
|
||||
}
|
||||
Err(ApiError::not_found_note())
|
||||
}
|
||||
|
||||
pub async fn public_page(
|
||||
State(state): State<SharedState>,
|
||||
headers: HeaderMap,
|
||||
Path(token): Path<String>,
|
||||
) -> Result<Json<PublicPageResponse>, ApiError> {
|
||||
let page = db::find_published_page(&state.db, &token)
|
||||
.await?
|
||||
.ok_or_else(ApiError::not_found_note)?;
|
||||
ensure_public_page_access(&state, &headers, &page).await?;
|
||||
Ok(Json(PublicPageResponse {
|
||||
title: page.title,
|
||||
content: page.content,
|
||||
@@ -240,12 +283,14 @@ pub async fn public_page(
|
||||
|
||||
pub async fn update_public_task(
|
||||
State(state): State<SharedState>,
|
||||
headers: HeaderMap,
|
||||
Path(token): Path<String>,
|
||||
Json(payload): Json<PublicTaskUpdateRequest>,
|
||||
) -> Result<Json<PublicPageResponse>, ApiError> {
|
||||
let current = db::find_published_page(&state.db, &token)
|
||||
.await?
|
||||
.ok_or_else(ApiError::not_found_note)?;
|
||||
ensure_public_page_access(&state, &headers, ¤t).await?;
|
||||
if !current.allow_task_updates {
|
||||
return Err(ApiError::forbidden(
|
||||
"Task updates are disabled for this page",
|
||||
@@ -357,13 +402,13 @@ pub(super) async fn authorized_pad(
|
||||
.ok_or_else(ApiError::not_found_note)?;
|
||||
let token_level = combined_token_access_level(state, "pad", slug, access_token, bearer).await?;
|
||||
if pad.is_private != 0 && token_level == AccessLevel::None {
|
||||
return Err(ApiError::forbidden("This note is private."));
|
||||
return Err(ApiError::not_found_note());
|
||||
}
|
||||
if pad.password_hash.is_some()
|
||||
&& !db::verify_pad_password(&pad, password)
|
||||
&& token_level == AccessLevel::None
|
||||
{
|
||||
return Err(ApiError::unauthorized());
|
||||
return Err(ApiError::forbidden("Password required or incorrect."));
|
||||
}
|
||||
Ok(pad)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user