fix auth and backend force
This commit is contained in:
Generated
+1
-1
@@ -2581,7 +2581,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustpad"
|
name = "rustpad"
|
||||||
version = "0.0.39"
|
version = "0.0.40"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"argon2",
|
"argon2",
|
||||||
"aws-config",
|
"aws-config",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "rustpad"
|
name = "rustpad"
|
||||||
version = "0.0.39"
|
version = "0.0.40"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
rust-version = "1.94"
|
rust-version = "1.94"
|
||||||
description = "Collaborative Markdown notepad built with Axum, WebSockets and SQLite, PostgreSQL and MySQL"
|
description = "Collaborative Markdown notepad built with Axum, WebSockets and SQLite, PostgreSQL and MySQL"
|
||||||
|
|||||||
+77
-4
@@ -167,16 +167,26 @@ pub async fn create_workspace(
|
|||||||
|
|
||||||
pub async fn workspace_info(
|
pub async fn workspace_info(
|
||||||
State(state): State<SharedState>,
|
State(state): State<SharedState>,
|
||||||
|
headers: HeaderMap,
|
||||||
Path(workspace_slug): Path<String>,
|
Path(workspace_slug): Path<String>,
|
||||||
) -> Result<Json<WorkspaceInfo>, ApiError> {
|
) -> Result<Json<WorkspaceInfo>, ApiError> {
|
||||||
let workspace = db::find_workspace(&state.db, &workspace_slug)
|
let workspace = db::find_workspace(&state.db, &workspace_slug)
|
||||||
.await?
|
.await?
|
||||||
.ok_or_else(ApiError::not_found_workspace)?;
|
.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)))
|
Ok(Json(workspace_info_from(&workspace)))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn open_workspace(
|
pub async fn open_workspace(
|
||||||
State(state): State<SharedState>,
|
State(state): State<SharedState>,
|
||||||
|
headers: HeaderMap,
|
||||||
Path(workspace_slug): Path<String>,
|
Path(workspace_slug): Path<String>,
|
||||||
Json(payload): Json<PasswordRequest>,
|
Json(payload): Json<PasswordRequest>,
|
||||||
) -> Result<Json<WorkspaceOpenResponse>, ApiError> {
|
) -> Result<Json<WorkspaceOpenResponse>, ApiError> {
|
||||||
@@ -185,6 +195,7 @@ pub async fn open_workspace(
|
|||||||
&workspace_slug,
|
&workspace_slug,
|
||||||
payload.password.as_deref(),
|
payload.password.as_deref(),
|
||||||
payload.access_token.as_deref(),
|
payload.access_token.as_deref(),
|
||||||
|
bearer_token(&headers),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
let notes = db::list_notes(&state.db, workspace.id)
|
let notes = db::list_notes(&state.db, workspace.id)
|
||||||
@@ -209,6 +220,7 @@ pub async fn open_workspace(
|
|||||||
|
|
||||||
pub async fn create_note(
|
pub async fn create_note(
|
||||||
State(state): State<SharedState>,
|
State(state): State<SharedState>,
|
||||||
|
headers: HeaderMap,
|
||||||
Path(workspace_slug): Path<String>,
|
Path(workspace_slug): Path<String>,
|
||||||
Json(payload): Json<CreateNoteRequest>,
|
Json(payload): Json<CreateNoteRequest>,
|
||||||
) -> Result<(StatusCode, Json<NoteListItem>), ApiError> {
|
) -> Result<(StatusCode, Json<NoteListItem>), ApiError> {
|
||||||
@@ -217,6 +229,7 @@ pub async fn create_note(
|
|||||||
&workspace_slug,
|
&workspace_slug,
|
||||||
payload.password.as_deref(),
|
payload.password.as_deref(),
|
||||||
payload.access_token.as_deref(),
|
payload.access_token.as_deref(),
|
||||||
|
bearer_token(&headers),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
let title = validate_name(&payload.name, "Note name")?;
|
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)
|
let workspace = db::find_workspace(&state.db, &workspace_slug)
|
||||||
.await?
|
.await?
|
||||||
.ok_or_else(ApiError::not_found_workspace)?;
|
.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, ¬e_slug)
|
let note = db::find_note(&state.db, workspace.id, ¬e_slug)
|
||||||
.await?
|
.await?
|
||||||
.ok_or_else(ApiError::not_found_note)?;
|
.ok_or_else(ApiError::not_found_note)?;
|
||||||
@@ -305,6 +326,7 @@ pub async fn note_info(
|
|||||||
|
|
||||||
pub async fn history(
|
pub async fn history(
|
||||||
State(state): State<SharedState>,
|
State(state): State<SharedState>,
|
||||||
|
headers: HeaderMap,
|
||||||
Path((workspace_slug, note_slug)): Path<(String, String)>,
|
Path((workspace_slug, note_slug)): Path<(String, String)>,
|
||||||
Json(payload): Json<PasswordRequest>,
|
Json(payload): Json<PasswordRequest>,
|
||||||
) -> Result<Json<Vec<db::Revision>>, ApiError> {
|
) -> Result<Json<Vec<db::Revision>>, ApiError> {
|
||||||
@@ -314,6 +336,7 @@ pub async fn history(
|
|||||||
¬e_slug,
|
¬e_slug,
|
||||||
payload.password.as_deref(),
|
payload.password.as_deref(),
|
||||||
payload.access_token.as_deref(),
|
payload.access_token.as_deref(),
|
||||||
|
bearer_token(&headers),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
let _ = workspace;
|
let _ = workspace;
|
||||||
@@ -330,6 +353,7 @@ pub async fn history(
|
|||||||
|
|
||||||
pub async fn restore(
|
pub async fn restore(
|
||||||
State(state): State<SharedState>,
|
State(state): State<SharedState>,
|
||||||
|
headers: HeaderMap,
|
||||||
Path((workspace_slug, note_slug)): Path<(String, String)>,
|
Path((workspace_slug, note_slug)): Path<(String, String)>,
|
||||||
Json(payload): Json<RestoreRequest>,
|
Json(payload): Json<RestoreRequest>,
|
||||||
) -> Result<Json<serde_json::Value>, ApiError> {
|
) -> Result<Json<serde_json::Value>, ApiError> {
|
||||||
@@ -339,6 +363,7 @@ pub async fn restore(
|
|||||||
¬e_slug,
|
¬e_slug,
|
||||||
payload.password.as_deref(),
|
payload.password.as_deref(),
|
||||||
payload.access_token.as_deref(),
|
payload.access_token.as_deref(),
|
||||||
|
bearer_token(&headers),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
let content: Option<String> = sqlx::query_scalar(queries::get(state.db.kind(), queries::Q028))
|
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})))
|
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(
|
pub async fn authorized_workspace(
|
||||||
state: &SharedState,
|
state: &SharedState,
|
||||||
slug: &str,
|
slug: &str,
|
||||||
password: Option<&str>,
|
password: Option<&str>,
|
||||||
access_token: Option<&str>,
|
access_token: Option<&str>,
|
||||||
|
bearer: Option<&str>,
|
||||||
) -> Result<db::Workspace, ApiError> {
|
) -> Result<db::Workspace, ApiError> {
|
||||||
let workspace = db::find_workspace(&state.db, slug)
|
let workspace = db::find_workspace(&state.db, slug)
|
||||||
.await?
|
.await?
|
||||||
.ok_or_else(ApiError::not_found_workspace)?;
|
.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 {
|
if workspace.is_private != 0 && !token_access {
|
||||||
return Err(ApiError::forbidden("This workspace is private."));
|
return Err(ApiError::forbidden("This workspace is private."));
|
||||||
}
|
}
|
||||||
@@ -398,8 +441,9 @@ async fn authorized_note(
|
|||||||
note_slug: &str,
|
note_slug: &str,
|
||||||
password: Option<&str>,
|
password: Option<&str>,
|
||||||
access_token: Option<&str>,
|
access_token: Option<&str>,
|
||||||
|
bearer: Option<&str>,
|
||||||
) -> Result<(db::Workspace, db::Note), ApiError> {
|
) -> 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)
|
let note = db::find_note(&state.db, workspace.id, note_slug)
|
||||||
.await?
|
.await?
|
||||||
.ok_or_else(ApiError::not_found_note)?;
|
.ok_or_else(ApiError::not_found_note)?;
|
||||||
@@ -551,6 +595,14 @@ pub async fn pad_info(
|
|||||||
let pad = db::find_pad(&state.db, &slug)
|
let pad = db::find_pad(&state.db, &slug)
|
||||||
.await?
|
.await?
|
||||||
.ok_or_else(ApiError::not_found_note)?;
|
.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 {
|
Ok(Json(PadInfo {
|
||||||
slug: pad.slug,
|
slug: pad.slug,
|
||||||
title: pad.title,
|
title: pad.title,
|
||||||
@@ -571,6 +623,7 @@ pub async fn pad_info(
|
|||||||
|
|
||||||
pub async fn publish_pad_page(
|
pub async fn publish_pad_page(
|
||||||
State(state): State<SharedState>,
|
State(state): State<SharedState>,
|
||||||
|
headers: HeaderMap,
|
||||||
Path(slug): Path<String>,
|
Path(slug): Path<String>,
|
||||||
Json(payload): Json<PublishRequest>,
|
Json(payload): Json<PublishRequest>,
|
||||||
) -> Result<Json<PublishResponse>, ApiError> {
|
) -> Result<Json<PublishResponse>, ApiError> {
|
||||||
@@ -579,6 +632,7 @@ pub async fn publish_pad_page(
|
|||||||
&slug,
|
&slug,
|
||||||
payload.password.as_deref(),
|
payload.password.as_deref(),
|
||||||
payload.access_token.as_deref(),
|
payload.access_token.as_deref(),
|
||||||
|
bearer_token(&headers),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
let token = db::publish_pad(&state.db, pad.id).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(
|
pub async fn publish_note_page(
|
||||||
State(state): State<SharedState>,
|
State(state): State<SharedState>,
|
||||||
|
headers: HeaderMap,
|
||||||
Path((workspace_slug, note_slug)): Path<(String, String)>,
|
Path((workspace_slug, note_slug)): Path<(String, String)>,
|
||||||
Json(payload): Json<PublishRequest>,
|
Json(payload): Json<PublishRequest>,
|
||||||
) -> Result<Json<PublishResponse>, ApiError> {
|
) -> Result<Json<PublishResponse>, ApiError> {
|
||||||
@@ -599,6 +654,7 @@ pub async fn publish_note_page(
|
|||||||
¬e_slug,
|
¬e_slug,
|
||||||
payload.password.as_deref(),
|
payload.password.as_deref(),
|
||||||
payload.access_token.as_deref(),
|
payload.access_token.as_deref(),
|
||||||
|
bearer_token(&headers),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
let token = db::publish_note(&state.db, note.id).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(
|
pub async fn pad_history(
|
||||||
State(state): State<SharedState>,
|
State(state): State<SharedState>,
|
||||||
|
headers: HeaderMap,
|
||||||
Path(slug): Path<String>,
|
Path(slug): Path<String>,
|
||||||
Json(payload): Json<PasswordRequest>,
|
Json(payload): Json<PasswordRequest>,
|
||||||
) -> Result<Json<Vec<db::Revision>>, ApiError> {
|
) -> Result<Json<Vec<db::Revision>>, ApiError> {
|
||||||
@@ -657,6 +714,7 @@ pub async fn pad_history(
|
|||||||
&slug,
|
&slug,
|
||||||
payload.password.as_deref(),
|
payload.password.as_deref(),
|
||||||
payload.access_token.as_deref(),
|
payload.access_token.as_deref(),
|
||||||
|
bearer_token(&headers),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
let revisions = db::list_pad_revisions(&state.db, pad.id)
|
let revisions = db::list_pad_revisions(&state.db, pad.id)
|
||||||
@@ -672,6 +730,7 @@ pub async fn pad_history(
|
|||||||
|
|
||||||
pub async fn pad_restore(
|
pub async fn pad_restore(
|
||||||
State(state): State<SharedState>,
|
State(state): State<SharedState>,
|
||||||
|
headers: HeaderMap,
|
||||||
Path(slug): Path<String>,
|
Path(slug): Path<String>,
|
||||||
Json(payload): Json<RestoreRequest>,
|
Json(payload): Json<RestoreRequest>,
|
||||||
) -> Result<Json<serde_json::Value>, ApiError> {
|
) -> Result<Json<serde_json::Value>, ApiError> {
|
||||||
@@ -680,6 +739,7 @@ pub async fn pad_restore(
|
|||||||
&slug,
|
&slug,
|
||||||
payload.password.as_deref(),
|
payload.password.as_deref(),
|
||||||
payload.access_token.as_deref(),
|
payload.access_token.as_deref(),
|
||||||
|
bearer_token(&headers),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
let content: Option<String> = sqlx::query_scalar(queries::get(state.db.kind(), queries::Q029))
|
let content: Option<String> = sqlx::query_scalar(queries::get(state.db.kind(), queries::Q029))
|
||||||
@@ -716,11 +776,13 @@ async fn authorized_pad(
|
|||||||
slug: &str,
|
slug: &str,
|
||||||
password: Option<&str>,
|
password: Option<&str>,
|
||||||
access_token: Option<&str>,
|
access_token: Option<&str>,
|
||||||
|
bearer: Option<&str>,
|
||||||
) -> Result<db::Pad, ApiError> {
|
) -> Result<db::Pad, ApiError> {
|
||||||
let pad = db::find_pad(&state.db, slug)
|
let pad = db::find_pad(&state.db, slug)
|
||||||
.await?
|
.await?
|
||||||
.ok_or_else(ApiError::not_found_note)?;
|
.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 {
|
if pad.is_private != 0 && !token_access {
|
||||||
return Err(ApiError::forbidden("This note is private."));
|
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(
|
pub async fn upload_pad_file(
|
||||||
State(state): State<SharedState>,
|
State(state): State<SharedState>,
|
||||||
|
headers: HeaderMap,
|
||||||
Path(slug): Path<String>,
|
Path(slug): Path<String>,
|
||||||
mut multipart: Multipart,
|
mut multipart: Multipart,
|
||||||
) -> Result<Json<serde_json::Value>, ApiError> {
|
) -> Result<Json<serde_json::Value>, ApiError> {
|
||||||
@@ -783,7 +846,7 @@ pub async fn upload_pad_file(
|
|||||||
file = Some((filename, bytes.to_vec()));
|
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 (original, bytes) = file.ok_or_else(|| ApiError::bad_request("No file provided"))?;
|
||||||
let safe = sanitize_filename(&original);
|
let safe = sanitize_filename(&original);
|
||||||
let file_token = db::pad_file_token(&state.db, pad.id).await?;
|
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(
|
pub async fn pad_files(
|
||||||
State(state): State<SharedState>,
|
State(state): State<SharedState>,
|
||||||
|
headers: HeaderMap,
|
||||||
Path(slug): Path<String>,
|
Path(slug): Path<String>,
|
||||||
Json(payload): Json<PasswordRequest>,
|
Json(payload): Json<PasswordRequest>,
|
||||||
) -> Result<Json<Vec<db::NoteFile>>, ApiError> {
|
) -> Result<Json<Vec<db::NoteFile>>, ApiError> {
|
||||||
@@ -831,6 +895,7 @@ pub async fn pad_files(
|
|||||||
&slug,
|
&slug,
|
||||||
payload.password.as_deref(),
|
payload.password.as_deref(),
|
||||||
payload.access_token.as_deref(),
|
payload.access_token.as_deref(),
|
||||||
|
bearer_token(&headers),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
let mut files = db::list_pad_files(&state.db, pad.id).await?;
|
let mut files = db::list_pad_files(&state.db, pad.id).await?;
|
||||||
@@ -861,6 +926,7 @@ pub async fn delete_pad_file(
|
|||||||
&slug,
|
&slug,
|
||||||
payload.password.as_deref(),
|
payload.password.as_deref(),
|
||||||
payload.access_token.as_deref(),
|
payload.access_token.as_deref(),
|
||||||
|
bearer_token(&headers),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
if !crate::auth::is_resource_owner(&state, "pad", &pad.slug, bearer_token(&headers))
|
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(
|
pub async fn upload_note_file(
|
||||||
State(state): State<SharedState>,
|
State(state): State<SharedState>,
|
||||||
|
headers: HeaderMap,
|
||||||
Path((workspace_slug, note_slug)): Path<(String, String)>,
|
Path((workspace_slug, note_slug)): Path<(String, String)>,
|
||||||
mut multipart: Multipart,
|
mut multipart: Multipart,
|
||||||
) -> Result<Json<serde_json::Value>, ApiError> {
|
) -> Result<Json<serde_json::Value>, ApiError> {
|
||||||
@@ -925,6 +992,7 @@ pub async fn upload_note_file(
|
|||||||
¬e_slug,
|
¬e_slug,
|
||||||
password.as_deref(),
|
password.as_deref(),
|
||||||
access_token.as_deref(),
|
access_token.as_deref(),
|
||||||
|
bearer_token(&headers),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
let (original, bytes) = file.ok_or_else(|| ApiError::bad_request("No file provided"))?;
|
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(
|
pub async fn delete_note(
|
||||||
State(state): State<SharedState>,
|
State(state): State<SharedState>,
|
||||||
|
headers: HeaderMap,
|
||||||
Path((workspace_slug, note_slug)): Path<(String, String)>,
|
Path((workspace_slug, note_slug)): Path<(String, String)>,
|
||||||
Json(payload): Json<PasswordRequest>,
|
Json(payload): Json<PasswordRequest>,
|
||||||
) -> Result<Json<serde_json::Value>, ApiError> {
|
) -> Result<Json<serde_json::Value>, ApiError> {
|
||||||
@@ -975,6 +1044,7 @@ pub async fn delete_note(
|
|||||||
¬e_slug,
|
¬e_slug,
|
||||||
payload.password.as_deref(),
|
payload.password.as_deref(),
|
||||||
payload.access_token.as_deref(),
|
payload.access_token.as_deref(),
|
||||||
|
bearer_token(&headers),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
if note.protected {
|
if note.protected {
|
||||||
@@ -993,6 +1063,7 @@ pub async fn delete_note(
|
|||||||
|
|
||||||
pub async fn note_files(
|
pub async fn note_files(
|
||||||
State(state): State<SharedState>,
|
State(state): State<SharedState>,
|
||||||
|
headers: HeaderMap,
|
||||||
Path((workspace_slug, note_slug)): Path<(String, String)>,
|
Path((workspace_slug, note_slug)): Path<(String, String)>,
|
||||||
Json(payload): Json<PasswordRequest>,
|
Json(payload): Json<PasswordRequest>,
|
||||||
) -> Result<Json<Vec<db::NoteFile>>, ApiError> {
|
) -> Result<Json<Vec<db::NoteFile>>, ApiError> {
|
||||||
@@ -1002,6 +1073,7 @@ pub async fn note_files(
|
|||||||
¬e_slug,
|
¬e_slug,
|
||||||
payload.password.as_deref(),
|
payload.password.as_deref(),
|
||||||
payload.access_token.as_deref(),
|
payload.access_token.as_deref(),
|
||||||
|
bearer_token(&headers),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
let mut files = db::list_note_files(&state.db, note.id).await?;
|
let mut files = db::list_note_files(&state.db, note.id).await?;
|
||||||
@@ -1033,6 +1105,7 @@ pub async fn delete_note_file(
|
|||||||
¬e_slug,
|
¬e_slug,
|
||||||
payload.password.as_deref(),
|
payload.password.as_deref(),
|
||||||
payload.access_token.as_deref(),
|
payload.access_token.as_deref(),
|
||||||
|
bearer_token(&headers),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
let workspace_owner = crate::auth::is_resource_owner(
|
let workspace_owner = crate::auth::is_resource_owner(
|
||||||
|
|||||||
@@ -48,6 +48,8 @@ export async function api(path, options = {}) {
|
|||||||
const timeout = setTimeout(() => controller.abort(), 12000);
|
const timeout = setTimeout(() => controller.abort(), 12000);
|
||||||
try {
|
try {
|
||||||
const headers = new Headers(options.headers || {});
|
const headers = new Headers(options.headers || {});
|
||||||
|
const authToken = localStorage.getItem("rustpad:auth-token") || sessionStorage.getItem("rustpad:auth-token");
|
||||||
|
if (authToken && !headers.has("authorization")) headers.set("authorization", `Bearer ${authToken}`);
|
||||||
if (!(options.body instanceof FormData) && !headers.has("content-type")) headers.set("content-type", "application/json");
|
if (!(options.body instanceof FormData) && !headers.has("content-type")) headers.set("content-type", "application/json");
|
||||||
const started = performance.now();
|
const started = performance.now();
|
||||||
logDebug("api.request", { method: options.method || "GET", path });
|
logDebug("api.request", { method: options.method || "GET", path });
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ export function startNoteEditor(adapter) {
|
|||||||
function currentOwner() { const color = currentUserColor(); return color ? `${nickname}\u001f${color}` : nickname; }
|
function currentOwner() { const color = currentUserColor(); return color ? `${nickname}\u001f${color}` : nickname; }
|
||||||
function updateCurrentUser() { const color = currentUserColor() || defaultColorFor(nickname); currentUser.querySelector(".user-chip__name").textContent = nickname; currentUser.style.setProperty("--owner", color); userColorPicker.value = /^#[0-9a-f]{6}$/i.test(color) ? color : "#7c6cff"; }
|
function updateCurrentUser() { const color = currentUserColor() || defaultColorFor(nickname); currentUser.querySelector(".user-chip__name").textContent = nickname; currentUser.style.setProperty("--owner", color); userColorPicker.value = /^#[0-9a-f]{6}$/i.test(color) ? color : "#7c6cff"; }
|
||||||
function toast(text) { const el = document.querySelector("#toast"); el.textContent = text; el.classList.add("visible"); setTimeout(() => el.classList.remove("visible"), 1800); }
|
function toast(text) { const el = document.querySelector("#toast"); el.textContent = text; el.classList.add("visible"); setTimeout(() => el.classList.remove("visible"), 1800); }
|
||||||
function sessionHeaders() { const token = getAuthToken(); return token ? { Authorization: `Bearer ${token}` } : {}; }
|
function sessionHeaders() { const token = accessToken || getAuthToken(); return token ? { Authorization: `Bearer ${token}` } : {}; }
|
||||||
async function loadNoteInfo() { info = await adapter.loadInfo(sessionHeaders()); return info; }
|
async function loadNoteInfo() { info = await adapter.loadInfo(sessionHeaders()); return info; }
|
||||||
function updatePresence(users) { const entries = Array.isArray(users) ? users : []; roomCount.textContent = `${entries.length} ${entries.length === 1 ? "user" : "users"}`; roomUsers.replaceChildren(...entries.map(entry => { const user = typeof entry === "string" ? { name: entry, color: "" } : entry || {}; const li = document.createElement("li"), dot = document.createElement("span"), label = document.createElement("span"); li.className = "room-user"; dot.className = "room-user__dot"; dot.style.setProperty("--owner", /^#[0-9a-f]{6}$/i.test(user.color || "") ? user.color : defaultColorFor(user.name)); label.textContent = user.name || "Guest"; li.title = label.textContent; li.append(dot, label); return li; })); if (!entries.length) { const li = document.createElement("li"); li.textContent = "No active users"; roomUsers.append(li); } }
|
function updatePresence(users) { const entries = Array.isArray(users) ? users : []; roomCount.textContent = `${entries.length} ${entries.length === 1 ? "user" : "users"}`; roomUsers.replaceChildren(...entries.map(entry => { const user = typeof entry === "string" ? { name: entry, color: "" } : entry || {}; const li = document.createElement("li"), dot = document.createElement("span"), label = document.createElement("span"); li.className = "room-user"; dot.className = "room-user__dot"; dot.style.setProperty("--owner", /^#[0-9a-f]{6}$/i.test(user.color || "") ? user.color : defaultColorFor(user.name)); label.textContent = user.name || "Guest"; li.title = label.textContent; li.append(dot, label); return li; })); if (!entries.length) { const li = document.createElement("li"); li.textContent = "No active users"; roomUsers.append(li); } }
|
||||||
function updateLatency(ms) { socketLatency.textContent = Number.isFinite(ms) ? `${ms} ms` : "— ms"; }
|
function updateLatency(ms) { socketLatency.textContent = Number.isFinite(ms) ? `${ms} ms` : "— ms"; }
|
||||||
|
|||||||
@@ -102,7 +102,8 @@ async function openWorkspace() {
|
|||||||
}
|
}
|
||||||
async function init() {
|
async function init() {
|
||||||
try {
|
try {
|
||||||
info = await api(`/api/workspaces/${encodeURIComponent(slug)}`);
|
const headers = accessToken ? { Authorization: `Bearer ${accessToken}` } : {};
|
||||||
|
info = await api(`/api/workspaces/${encodeURIComponent(slug)}`, { headers });
|
||||||
document.querySelector("#workspace-title").textContent = info.title;
|
document.querySelector("#workspace-title").textContent = info.title;
|
||||||
document.querySelector("#workspace-url").textContent = location.pathname;
|
document.querySelector("#workspace-url").textContent = location.pathname;
|
||||||
if (info.protected && !accessToken) dialog.showModal(); else openWorkspace();
|
if (info.protected && !accessToken) dialog.showModal(); else openWorkspace();
|
||||||
|
|||||||
Reference in New Issue
Block a user