license and some functions
This commit is contained in:
+99
-27
@@ -1,3 +1,12 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Mateusz Gruszczyński @linuxiarz.pl
|
||||
* Source-Available Code / Dual-Licensed.
|
||||
*
|
||||
* Free for non-commercial and evaluation use under terms of BSL/GPLv3.
|
||||
* Commercial or production use requires a valid paid license.
|
||||
* See LICENSE file in repository root for details.
|
||||
*/
|
||||
|
||||
use super::*;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
@@ -84,8 +93,17 @@ pub async fn pad_info(
|
||||
.await?;
|
||||
let (global_color, note_color) = editor_colors(&state, &headers, "pad", &slug).await?;
|
||||
let (authorship_mode, colors_enabled) = editor_settings(&state, "pad", &slug).await?;
|
||||
let can_save_editor_settings = crate::auth::resource_permission(&state, "pad", &slug, bearer_token(&headers)).await.ok().flatten().as_deref() == Some("rw");
|
||||
if pad.is_private == 0 && !db::pad_public_page_disabled(&state.db, pad.id).await? && !db::pad_public_page_enabled(&state.db, pad.id).await? {
|
||||
let can_save_editor_settings =
|
||||
crate::auth::resource_permission(&state, "pad", &slug, bearer_token(&headers))
|
||||
.await
|
||||
.ok()
|
||||
.flatten()
|
||||
.as_deref()
|
||||
== Some("rw");
|
||||
if pad.is_private == 0
|
||||
&& !db::pad_public_page_disabled(&state.db, pad.id).await?
|
||||
&& !db::pad_public_page_enabled(&state.db, pad.id).await?
|
||||
{
|
||||
db::publish_pad(&state.db, pad.id).await?;
|
||||
}
|
||||
Ok(Json(PadInfo {
|
||||
@@ -115,7 +133,9 @@ pub async fn pad_info(
|
||||
}
|
||||
|
||||
pub async fn set_pad_editor_settings(
|
||||
State(state): State<SharedState>, headers: HeaderMap, Path(slug): Path<String>,
|
||||
State(state): State<SharedState>,
|
||||
headers: HeaderMap,
|
||||
Path(slug): Path<String>,
|
||||
Json(payload): Json<EditorSettingsRequest>,
|
||||
) -> Result<Json<serde_json::Value>, ApiError> {
|
||||
save_editor_settings(&state, &headers, "pad", &slug, "pad", &slug, payload).await
|
||||
@@ -207,14 +227,18 @@ pub async fn publish_pad_page(
|
||||
if !enabled {
|
||||
db::unpublish_pad(&state.db, pad.id).await?;
|
||||
db::set_pad_public_page_disabled(&state.db, pad.id, true).await?;
|
||||
return Ok(Json(PublishResponse { url: None, enabled: false }));
|
||||
return Ok(Json(PublishResponse {
|
||||
url: None,
|
||||
enabled: false,
|
||||
}));
|
||||
}
|
||||
db::set_pad_public_page_disabled(&state.db, pad.id, false).await?;
|
||||
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: Some(format!("/s/{token}")), enabled: true,
|
||||
url: Some(format!("/s/{token}")),
|
||||
enabled: true,
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -252,50 +276,99 @@ pub async fn publish_note_page(
|
||||
if !enabled {
|
||||
db::unpublish_note(&state.db, note.id).await?;
|
||||
db::set_note_public_page_disabled(&state.db, note.id, true).await?;
|
||||
return Ok(Json(PublishResponse { url: None, enabled: false }));
|
||||
return Ok(Json(PublishResponse {
|
||||
url: None,
|
||||
enabled: false,
|
||||
}));
|
||||
}
|
||||
db::set_note_public_page_disabled(&state.db, note.id, false).await?;
|
||||
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: Some(format!("/s/{token}")), enabled: true,
|
||||
url: Some(format!("/s/{token}")),
|
||||
enabled: true,
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
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())
|
||||
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> {
|
||||
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(()); }
|
||||
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.")) };
|
||||
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(()); }
|
||||
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 = ?",
|
||||
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."))
|
||||
};
|
||||
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())
|
||||
}
|
||||
@@ -461,4 +534,3 @@ async fn unique_pad_slug(state: &SharedState, base: &str) -> Result<String, ApiE
|
||||
}
|
||||
Err(ApiError::internal("Failed to create a unique address"))
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user