fix auth and backend force

This commit is contained in:
Mateusz Gruszczyński
2026-07-26 16:06:30 +02:00
parent f145c3c121
commit 9b89ef79c4
6 changed files with 84 additions and 8 deletions
+77 -4
View File
@@ -167,16 +167,26 @@ pub async fn create_workspace(
pub async fn workspace_info(
State(state): State<SharedState>,
headers: HeaderMap,
Path(workspace_slug): Path<String>,
) -> Result<Json<WorkspaceInfo>, ApiError> {
let workspace = db::find_workspace(&state.db, &workspace_slug)
.await?
.ok_or_else(ApiError::not_found_workspace)?;
ensure_private_resource_access(
&state,
"workspace",
&workspace.slug,
workspace.is_private,
bearer_token(&headers),
)
.await?;
Ok(Json(workspace_info_from(&workspace)))
}
pub async fn open_workspace(
State(state): State<SharedState>,
headers: HeaderMap,
Path(workspace_slug): Path<String>,
Json(payload): Json<PasswordRequest>,
) -> Result<Json<WorkspaceOpenResponse>, ApiError> {
@@ -185,6 +195,7 @@ pub async fn open_workspace(
&workspace_slug,
payload.password.as_deref(),
payload.access_token.as_deref(),
bearer_token(&headers),
)
.await?;
let notes = db::list_notes(&state.db, workspace.id)
@@ -209,6 +220,7 @@ pub async fn open_workspace(
pub async fn create_note(
State(state): State<SharedState>,
headers: HeaderMap,
Path(workspace_slug): Path<String>,
Json(payload): Json<CreateNoteRequest>,
) -> Result<(StatusCode, Json<NoteListItem>), ApiError> {
@@ -217,6 +229,7 @@ pub async fn create_note(
&workspace_slug,
payload.password.as_deref(),
payload.access_token.as_deref(),
bearer_token(&headers),
)
.await?;
let title = validate_name(&payload.name, "Note name")?;
@@ -265,6 +278,14 @@ pub async fn note_info(
let workspace = db::find_workspace(&state.db, &workspace_slug)
.await?
.ok_or_else(ApiError::not_found_workspace)?;
ensure_private_resource_access(
&state,
"workspace",
&workspace.slug,
workspace.is_private,
bearer_token(&headers),
)
.await?;
let note = db::find_note(&state.db, workspace.id, &note_slug)
.await?
.ok_or_else(ApiError::not_found_note)?;
@@ -305,6 +326,7 @@ pub async fn note_info(
pub async fn history(
State(state): State<SharedState>,
headers: HeaderMap,
Path((workspace_slug, note_slug)): Path<(String, String)>,
Json(payload): Json<PasswordRequest>,
) -> Result<Json<Vec<db::Revision>>, ApiError> {
@@ -314,6 +336,7 @@ pub async fn history(
&note_slug,
payload.password.as_deref(),
payload.access_token.as_deref(),
bearer_token(&headers),
)
.await?;
let _ = workspace;
@@ -330,6 +353,7 @@ pub async fn history(
pub async fn restore(
State(state): State<SharedState>,
headers: HeaderMap,
Path((workspace_slug, note_slug)): Path<(String, String)>,
Json(payload): Json<RestoreRequest>,
) -> Result<Json<serde_json::Value>, ApiError> {
@@ -339,6 +363,7 @@ pub async fn restore(
&note_slug,
payload.password.as_deref(),
payload.access_token.as_deref(),
bearer_token(&headers),
)
.await?;
let content: Option<String> = sqlx::query_scalar(queries::get(state.db.kind(), queries::Q028))
@@ -370,16 +395,34 @@ pub async fn restore(
Ok(Json(serde_json::json!({"ok": true})))
}
async fn ensure_private_resource_access(
state: &SharedState,
kind: &str,
slug: &str,
is_private: i64,
token: Option<&str>,
) -> Result<(), ApiError> {
if is_private == 0 {
return Ok(());
}
if verify_resource_access_token(state, kind, slug, token).await? {
return Ok(());
}
Err(ApiError::forbidden("This resource is private."))
}
pub async fn authorized_workspace(
state: &SharedState,
slug: &str,
password: Option<&str>,
access_token: Option<&str>,
bearer: Option<&str>,
) -> Result<db::Workspace, ApiError> {
let workspace = db::find_workspace(&state.db, slug)
.await?
.ok_or_else(ApiError::not_found_workspace)?;
let token_access = verify_resource_access_token(state, "workspace", slug, access_token).await?;
let token_access = verify_resource_access_token(state, "workspace", slug, access_token).await?
|| verify_resource_access_token(state, "workspace", slug, bearer).await?;
if workspace.is_private != 0 && !token_access {
return Err(ApiError::forbidden("This workspace is private."));
}
@@ -398,8 +441,9 @@ async fn authorized_note(
note_slug: &str,
password: Option<&str>,
access_token: Option<&str>,
bearer: Option<&str>,
) -> Result<(db::Workspace, db::Note), ApiError> {
let workspace = authorized_workspace(state, workspace_slug, password, access_token).await?;
let workspace = authorized_workspace(state, workspace_slug, password, access_token, bearer).await?;
let note = db::find_note(&state.db, workspace.id, note_slug)
.await?
.ok_or_else(ApiError::not_found_note)?;
@@ -551,6 +595,14 @@ pub async fn pad_info(
let pad = db::find_pad(&state.db, &slug)
.await?
.ok_or_else(ApiError::not_found_note)?;
ensure_private_resource_access(
&state,
"pad",
&pad.slug,
pad.is_private,
bearer_token(&headers),
)
.await?;
Ok(Json(PadInfo {
slug: pad.slug,
title: pad.title,
@@ -571,6 +623,7 @@ pub async fn pad_info(
pub async fn publish_pad_page(
State(state): State<SharedState>,
headers: HeaderMap,
Path(slug): Path<String>,
Json(payload): Json<PublishRequest>,
) -> Result<Json<PublishResponse>, ApiError> {
@@ -579,6 +632,7 @@ pub async fn publish_pad_page(
&slug,
payload.password.as_deref(),
payload.access_token.as_deref(),
bearer_token(&headers),
)
.await?;
let token = db::publish_pad(&state.db, pad.id).await?;
@@ -590,6 +644,7 @@ pub async fn publish_pad_page(
pub async fn publish_note_page(
State(state): State<SharedState>,
headers: HeaderMap,
Path((workspace_slug, note_slug)): Path<(String, String)>,
Json(payload): Json<PublishRequest>,
) -> Result<Json<PublishResponse>, ApiError> {
@@ -599,6 +654,7 @@ pub async fn publish_note_page(
&note_slug,
payload.password.as_deref(),
payload.access_token.as_deref(),
bearer_token(&headers),
)
.await?;
let token = db::publish_note(&state.db, note.id).await?;
@@ -649,6 +705,7 @@ pub async fn update_public_task(
pub async fn pad_history(
State(state): State<SharedState>,
headers: HeaderMap,
Path(slug): Path<String>,
Json(payload): Json<PasswordRequest>,
) -> Result<Json<Vec<db::Revision>>, ApiError> {
@@ -657,6 +714,7 @@ pub async fn pad_history(
&slug,
payload.password.as_deref(),
payload.access_token.as_deref(),
bearer_token(&headers),
)
.await?;
let revisions = db::list_pad_revisions(&state.db, pad.id)
@@ -672,6 +730,7 @@ pub async fn pad_history(
pub async fn pad_restore(
State(state): State<SharedState>,
headers: HeaderMap,
Path(slug): Path<String>,
Json(payload): Json<RestoreRequest>,
) -> Result<Json<serde_json::Value>, ApiError> {
@@ -680,6 +739,7 @@ pub async fn pad_restore(
&slug,
payload.password.as_deref(),
payload.access_token.as_deref(),
bearer_token(&headers),
)
.await?;
let content: Option<String> = sqlx::query_scalar(queries::get(state.db.kind(), queries::Q029))
@@ -716,11 +776,13 @@ async fn authorized_pad(
slug: &str,
password: Option<&str>,
access_token: Option<&str>,
bearer: Option<&str>,
) -> Result<db::Pad, ApiError> {
let pad = db::find_pad(&state.db, slug)
.await?
.ok_or_else(ApiError::not_found_note)?;
let token_access = verify_resource_access_token(state, "pad", slug, access_token).await?;
let token_access = verify_resource_access_token(state, "pad", slug, access_token).await?
|| verify_resource_access_token(state, "pad", slug, bearer).await?;
if pad.is_private != 0 && !token_access {
return Err(ApiError::forbidden("This note is private."));
}
@@ -745,6 +807,7 @@ async fn unique_pad_slug(state: &SharedState, base: &str) -> Result<String, ApiE
pub async fn upload_pad_file(
State(state): State<SharedState>,
headers: HeaderMap,
Path(slug): Path<String>,
mut multipart: Multipart,
) -> Result<Json<serde_json::Value>, ApiError> {
@@ -783,7 +846,7 @@ pub async fn upload_pad_file(
file = Some((filename, bytes.to_vec()));
}
}
let pad = authorized_pad(&state, &slug, password.as_deref(), access_token.as_deref()).await?;
let pad = authorized_pad(&state, &slug, password.as_deref(), access_token.as_deref(), bearer_token(&headers)).await?;
let (original, bytes) = file.ok_or_else(|| ApiError::bad_request("No file provided"))?;
let safe = sanitize_filename(&original);
let file_token = db::pad_file_token(&state.db, pad.id).await?;
@@ -823,6 +886,7 @@ pub async fn upload_pad_file(
pub async fn pad_files(
State(state): State<SharedState>,
headers: HeaderMap,
Path(slug): Path<String>,
Json(payload): Json<PasswordRequest>,
) -> Result<Json<Vec<db::NoteFile>>, ApiError> {
@@ -831,6 +895,7 @@ pub async fn pad_files(
&slug,
payload.password.as_deref(),
payload.access_token.as_deref(),
bearer_token(&headers),
)
.await?;
let mut files = db::list_pad_files(&state.db, pad.id).await?;
@@ -861,6 +926,7 @@ pub async fn delete_pad_file(
&slug,
payload.password.as_deref(),
payload.access_token.as_deref(),
bearer_token(&headers),
)
.await?;
if !crate::auth::is_resource_owner(&state, "pad", &pad.slug, bearer_token(&headers))
@@ -881,6 +947,7 @@ pub async fn delete_pad_file(
pub async fn upload_note_file(
State(state): State<SharedState>,
headers: HeaderMap,
Path((workspace_slug, note_slug)): Path<(String, String)>,
mut multipart: Multipart,
) -> Result<Json<serde_json::Value>, ApiError> {
@@ -925,6 +992,7 @@ pub async fn upload_note_file(
&note_slug,
password.as_deref(),
access_token.as_deref(),
bearer_token(&headers),
)
.await?;
let (original, bytes) = file.ok_or_else(|| ApiError::bad_request("No file provided"))?;
@@ -966,6 +1034,7 @@ pub async fn upload_note_file(
pub async fn delete_note(
State(state): State<SharedState>,
headers: HeaderMap,
Path((workspace_slug, note_slug)): Path<(String, String)>,
Json(payload): Json<PasswordRequest>,
) -> Result<Json<serde_json::Value>, ApiError> {
@@ -975,6 +1044,7 @@ pub async fn delete_note(
&note_slug,
payload.password.as_deref(),
payload.access_token.as_deref(),
bearer_token(&headers),
)
.await?;
if note.protected {
@@ -993,6 +1063,7 @@ pub async fn delete_note(
pub async fn note_files(
State(state): State<SharedState>,
headers: HeaderMap,
Path((workspace_slug, note_slug)): Path<(String, String)>,
Json(payload): Json<PasswordRequest>,
) -> Result<Json<Vec<db::NoteFile>>, ApiError> {
@@ -1002,6 +1073,7 @@ pub async fn note_files(
&note_slug,
payload.password.as_deref(),
payload.access_token.as_deref(),
bearer_token(&headers),
)
.await?;
let mut files = db::list_note_files(&state.db, note.id).await?;
@@ -1033,6 +1105,7 @@ pub async fn delete_note_file(
&note_slug,
payload.password.as_deref(),
payload.access_token.as_deref(),
bearer_token(&headers),
)
.await?;
let workspace_owner = crate::auth::is_resource_owner(