new finctions and fixes
This commit is contained in:
Generated
+1
-1
@@ -2581,7 +2581,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustpad"
|
name = "rustpad"
|
||||||
version = "0.2.4"
|
version = "0.2.6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"argon2",
|
"argon2",
|
||||||
"aws-config",
|
"aws-config",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "rustpad"
|
name = "rustpad"
|
||||||
version = "0.2.5"
|
version = "0.2.6"
|
||||||
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"
|
||||||
|
|||||||
+42
-6
@@ -16,7 +16,6 @@ pub async fn upload_pad_file(
|
|||||||
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> {
|
||||||
require_authenticated_upload(&state, &headers).await?;
|
|
||||||
let mut password: Option<String> = None;
|
let mut password: Option<String> = None;
|
||||||
let mut access_token: Option<String> = None;
|
let mut access_token: Option<String> = None;
|
||||||
let mut file: Option<(String, Vec<u8>)> = None;
|
let mut file: Option<(String, Vec<u8>)> = None;
|
||||||
@@ -61,6 +60,14 @@ pub async fn upload_pad_file(
|
|||||||
&headers,
|
&headers,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
require_upload_permission(
|
||||||
|
&state,
|
||||||
|
&headers,
|
||||||
|
"pad",
|
||||||
|
&slug,
|
||||||
|
resource_request_token(&headers, "pad", &slug, access_token.as_deref()),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
let level = if db::verify_pad_password(&pad, password.as_deref())
|
let level = if db::verify_pad_password(&pad, password.as_deref())
|
||||||
|| (pad.is_private == 0 && pad.password_hash.is_none())
|
|| (pad.is_private == 0 && pad.password_hash.is_none())
|
||||||
{
|
{
|
||||||
@@ -205,7 +212,6 @@ pub async fn upload_note_file(
|
|||||||
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> {
|
||||||
require_authenticated_upload(&state, &headers).await?;
|
|
||||||
let mut password: Option<String> = None;
|
let mut password: Option<String> = None;
|
||||||
let mut access_token: Option<String> = None;
|
let mut access_token: Option<String> = None;
|
||||||
let mut file: Option<(String, Vec<u8>)> = None;
|
let mut file: Option<(String, Vec<u8>)> = None;
|
||||||
@@ -252,6 +258,20 @@ pub async fn upload_note_file(
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
require_upload_permission(
|
||||||
|
&state,
|
||||||
|
&headers,
|
||||||
|
"workspace",
|
||||||
|
&workspace_slug,
|
||||||
|
resource_request_token(
|
||||||
|
&headers,
|
||||||
|
"workspace",
|
||||||
|
&workspace_slug,
|
||||||
|
access_token.as_deref(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
let level = if db::verify_workspace_password(&workspace, password.as_deref())
|
let level = if db::verify_workspace_password(&workspace, password.as_deref())
|
||||||
|| (workspace.is_private == 0 && workspace.password_hash.is_none())
|
|| (workspace.is_private == 0 && workspace.password_hash.is_none())
|
||||||
{
|
{
|
||||||
@@ -438,17 +458,33 @@ pub async fn delete_note_file(
|
|||||||
Ok(Json(serde_json::json!({"ok": true})))
|
Ok(Json(serde_json::json!({"ok": true})))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn require_authenticated_upload(
|
async fn require_upload_permission(
|
||||||
state: &SharedState,
|
state: &SharedState,
|
||||||
headers: &HeaderMap,
|
headers: &HeaderMap,
|
||||||
|
kind: &str,
|
||||||
|
slug: &str,
|
||||||
|
resource_token: Option<&str>,
|
||||||
) -> Result<(), ApiError> {
|
) -> Result<(), ApiError> {
|
||||||
|
let permission = crate::auth::share_link_permission(state, kind, slug, resource_token)
|
||||||
|
.await
|
||||||
|
.map_err(|error| ApiError::forbidden(&error.message))?;
|
||||||
|
if permission.as_deref() == Some("rw") {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
let user = crate::auth::optional_user(state, headers)
|
let user = crate::auth::optional_user(state, headers)
|
||||||
.await
|
.await
|
||||||
.map_err(|error| ApiError::forbidden(&error.message))?;
|
.map_err(|error| ApiError::forbidden(&error.message))?;
|
||||||
if user.is_none() {
|
if user.is_some() {
|
||||||
return Err(ApiError::forbidden("Log in to upload files."));
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
match permission.as_deref() {
|
||||||
|
Some("ro") => Err(ApiError::forbidden("Read-only access.")),
|
||||||
|
_ => Err(ApiError::forbidden(
|
||||||
|
"Log in or use a read-write share link to upload files.",
|
||||||
|
)),
|
||||||
}
|
}
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn download_file(
|
pub async fn download_file(
|
||||||
|
|||||||
@@ -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 axum::http::{HeaderMap, HeaderValue, Uri, header};
|
use axum::http::{HeaderMap, HeaderValue, Uri, header};
|
||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
|
|
||||||
|
|||||||
@@ -5433,4 +5433,4 @@ dialog::backdrop {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
+15
-10
@@ -125,8 +125,9 @@
|
|||||||
aria-hidden="true"></span><span id="authorship-colors-label">Colors
|
aria-hidden="true"></span><span id="authorship-colors-label">Colors
|
||||||
on</span></label>
|
on</span></label>
|
||||||
<div class="authorship-mode-control" role="group" aria-label="Authorship display"><button
|
<div class="authorship-mode-control" role="group" aria-label="Authorship display"><button
|
||||||
type="button" data-authorship-mode="simple" class="active" disabled>Simple</button><button
|
type="button" data-authorship-mode="simple" class="active"
|
||||||
type="button" data-authorship-mode="full" disabled>Full</button></div>
|
disabled>Simple</button><button type="button" data-authorship-mode="full"
|
||||||
|
disabled>Full</button></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="participant-badges" class="participant-badges" aria-label="Participants"></div>
|
<div id="participant-badges" class="participant-badges" aria-label="Participants"></div>
|
||||||
@@ -218,9 +219,9 @@
|
|||||||
guest</button><button id="show-register" class="text-button" type="button">Register</button><button
|
guest</button><button id="show-register" class="text-button" type="button">Register</button><button
|
||||||
id="show-login" class="text-button" type="button">Log in</button></div>
|
id="show-login" class="text-button" type="button">Log in</button></div>
|
||||||
<section id="auth-panel" class="auth-panel" hidden>
|
<section id="auth-panel" class="auth-panel" hidden>
|
||||||
<h3 id="auth-mode-title">Log in</h3><label id="auth-email-field">E-mail / organization login<input
|
<h3 id="auth-mode-title">Log in</h3><label id="auth-email-field">E-mail / LDAP or AD Username<input
|
||||||
id="auth-email" name="username" type="email" maxlength="320" autocomplete="username"
|
id="auth-email" name="username" type="email" maxlength="320" autocomplete="username"
|
||||||
placeholder="you@example.com"></label><label>Password<input
|
placeholder="you@example.com or name.second-name"></label><label>Password<input
|
||||||
id="auth-password" name="password" type="password" minlength="8" maxlength="128"
|
id="auth-password" name="password" type="password" minlength="8" maxlength="128"
|
||||||
autocomplete="current-password"></label><button id="auth-submit" class="primary-button"
|
autocomplete="current-password"></label><button id="auth-submit" class="primary-button"
|
||||||
type="submit">Log in and continue</button>
|
type="submit">Log in and continue</button>
|
||||||
@@ -234,8 +235,8 @@
|
|||||||
</dialog>
|
</dialog>
|
||||||
<dialog id="password-dialog">
|
<dialog id="password-dialog">
|
||||||
<form id="password-form" class="dialog-panel">
|
<form id="password-form" class="dialog-panel">
|
||||||
<h2>Protected __PROTECTED_RESOURCE_LABEL__</h2><input id="open-password" type="password" autocomplete="current-password" required
|
<h2>Protected __PROTECTED_RESOURCE_LABEL__</h2><input id="open-password" type="password"
|
||||||
placeholder="Password">
|
autocomplete="current-password" required placeholder="Password">
|
||||||
<p id="password-error" class="form-message error"></p><button class="primary-button">Open</button><a
|
<p id="password-error" class="form-message error"></p><button class="primary-button">Open</button><a
|
||||||
class="dialog-link" href="/">Cancel</a>
|
class="dialog-link" href="/">Cancel</a>
|
||||||
</form>
|
</form>
|
||||||
@@ -261,10 +262,14 @@
|
|||||||
<option value="20">20</option>
|
<option value="20">20</option>
|
||||||
<option value="22">22</option>
|
<option value="22">22</option>
|
||||||
</select></label>
|
</select></label>
|
||||||
<label class="public-task-toggle mobile-option-check"><input id="mobile-line-numbers-toggle" type="checkbox"> Editor lines</label>
|
<label class="public-task-toggle mobile-option-check"><input id="mobile-line-numbers-toggle"
|
||||||
<label class="public-task-toggle mobile-option-check"><input id="mobile-preview-line-numbers-toggle" type="checkbox"> Preview lines</label>
|
type="checkbox"> Editor lines</label>
|
||||||
<label class="public-task-toggle mobile-option-check"><input id="mobile-compact-toggle" type="checkbox"> Compact view</label>
|
<label class="public-task-toggle mobile-option-check"><input id="mobile-preview-line-numbers-toggle"
|
||||||
<label class="public-task-toggle mobile-option-check"><input id="mobile-line-links-toggle" type="checkbox"> Line links</label>
|
type="checkbox"> Preview lines</label>
|
||||||
|
<label class="public-task-toggle mobile-option-check"><input id="mobile-compact-toggle" type="checkbox">
|
||||||
|
Compact view</label>
|
||||||
|
<label class="public-task-toggle mobile-option-check"><input id="mobile-line-links-toggle"
|
||||||
|
type="checkbox"> Line links</label>
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
<label id="mobile-color-button" class="mobile-color-button" title="Editor color"
|
<label id="mobile-color-button" class="mobile-color-button" title="Editor color"
|
||||||
|
|||||||
+1
-1
@@ -101,7 +101,7 @@
|
|||||||
<div class="identity-fields">
|
<div class="identity-fields">
|
||||||
<label>Nickname<input id="nickname" name="nickname" maxlength="40" autocomplete="off" data-bwignore="true"
|
<label>Nickname<input id="nickname" name="nickname" maxlength="40" autocomplete="off" data-bwignore="true"
|
||||||
placeholder="Your nickname"></label>
|
placeholder="Your nickname"></label>
|
||||||
<label id="auth-email-field">E-mail / LDAP Username<input id="auth-email" name="username" type="email"
|
<label id="auth-email-field">E-mail / LDAP or AD Username<input id="auth-email" name="username" type="email"
|
||||||
maxlength="320" autocomplete="username" required placeholder="you@example.com or name.second-name"></label>
|
maxlength="320" autocomplete="username" required placeholder="you@example.com or name.second-name"></label>
|
||||||
<label>Password<input id="auth-password" name="password" type="password" minlength="8" maxlength="128"
|
<label>Password<input id="auth-password" name="password" type="password" minlength="8" maxlength="128"
|
||||||
autocomplete="current-password" required placeholder="At least 8 characters"></label>
|
autocomplete="current-password" required placeholder="At least 8 characters"></label>
|
||||||
|
|||||||
@@ -75,7 +75,10 @@ export function bindNoteFiles({ editor, endpoints, getAccessToken, canDelete, to
|
|||||||
}
|
}
|
||||||
|
|
||||||
document.querySelector("#upload-button").addEventListener("click", () => {
|
document.querySelector("#upload-button").addEventListener("click", () => {
|
||||||
if (!getAuthToken()) { toast("Log in to upload files."); return; }
|
if (!getAuthToken() && !getAccessToken()) {
|
||||||
|
toast("Log in or use a read-write share link to upload files.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
input.click();
|
input.click();
|
||||||
});
|
});
|
||||||
input.addEventListener("change", async event => {
|
input.addEventListener("change", async event => {
|
||||||
|
|||||||
Reference in New Issue
Block a user