tokens and more
This commit is contained in:
+84
-42
@@ -38,6 +38,7 @@ const MAX_NAME_LENGTH: usize = 80;
|
||||
const MIN_PASSWORD_LENGTH: usize = 8;
|
||||
const MAX_PASSWORD_LENGTH: usize = 128;
|
||||
const MIN_WORKSPACE_SLUG_LENGTH: usize = 6;
|
||||
const MAX_DOCUMENT_SIZE_BYTES: usize = 2_000_000;
|
||||
|
||||
fn bearer_token(headers: &HeaderMap) -> Option<&str> {
|
||||
crate::security::session_token(headers)
|
||||
@@ -81,9 +82,9 @@ fn requester_guest_id(headers: &HeaderMap) -> Option<&str> {
|
||||
.map(str::trim)
|
||||
.filter(|value| {
|
||||
(16..=64).contains(&value.len())
|
||||
&& value
|
||||
.chars()
|
||||
.all(|character| character.is_ascii_alphanumeric() || matches!(character, '-' | '_'))
|
||||
&& value.chars().all(|character| {
|
||||
character.is_ascii_alphanumeric() || matches!(character, '-' | '_')
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -109,9 +110,7 @@ fn pad_creator_is_requester(headers: &HeaderMap, pad: &db::Pad) -> bool {
|
||||
pad.created_by_guest_id
|
||||
.as_deref()
|
||||
.zip(requester_guest_id(headers))
|
||||
.is_some_and(|(owner_guest_id, requester_guest_id)| {
|
||||
owner_guest_id == requester_guest_id
|
||||
})
|
||||
.is_some_and(|(owner_guest_id, requester_guest_id)| owner_guest_id == requester_guest_id)
|
||||
}
|
||||
|
||||
async fn has_write_permission(
|
||||
@@ -126,7 +125,8 @@ async fn has_write_permission(
|
||||
}
|
||||
let authorization = authorization_token(headers);
|
||||
if authorization != resource
|
||||
&& external_token_access_level(state, kind, slug, authorization).await? >= AccessLevel::Write
|
||||
&& external_token_access_level(state, kind, slug, authorization).await?
|
||||
>= AccessLevel::Write
|
||||
{
|
||||
return Ok(true);
|
||||
}
|
||||
@@ -191,10 +191,7 @@ pub(crate) async fn markdown_file_references(
|
||||
})
|
||||
.map(|file| MarkdownFileReference {
|
||||
filename: file.filename,
|
||||
url: crate::file_urls::public_file_url(
|
||||
state.files_public_url.as_deref(),
|
||||
&file.url,
|
||||
),
|
||||
url: crate::file_urls::public_file_url(state.files_public_url.as_deref(), &file.url),
|
||||
mime_type: file.mime_type,
|
||||
})
|
||||
.collect())
|
||||
@@ -253,6 +250,8 @@ pub struct PublicTaskUpdateRequest {
|
||||
pub struct CreateNoteRequest {
|
||||
name: String,
|
||||
#[serde(default)]
|
||||
content: Option<String>,
|
||||
#[serde(default)]
|
||||
password: Option<String>,
|
||||
#[serde(default)]
|
||||
access_token: Option<String>,
|
||||
@@ -385,14 +384,7 @@ async fn save_editor_settings(
|
||||
creator_can_manage_authorship: bool,
|
||||
payload: EditorSettingsRequest,
|
||||
) -> Result<Json<serde_json::Value>, ApiError> {
|
||||
if !has_write_permission(
|
||||
state,
|
||||
headers,
|
||||
permission_kind,
|
||||
permission_slug,
|
||||
)
|
||||
.await?
|
||||
{
|
||||
if !has_write_permission(state, headers, permission_kind, permission_slug).await? {
|
||||
return Err(ApiError::forbidden(
|
||||
"Read and write access is required to save editor preferences",
|
||||
));
|
||||
@@ -416,13 +408,13 @@ async fn save_editor_settings(
|
||||
let can_manage_authorship = if wants_global_update {
|
||||
creator_can_manage_authorship
|
||||
|| crate::auth::is_resource_owner(
|
||||
state,
|
||||
permission_kind,
|
||||
permission_slug,
|
||||
user_session_token(headers),
|
||||
)
|
||||
.await
|
||||
.unwrap_or(false)
|
||||
state,
|
||||
permission_kind,
|
||||
permission_slug,
|
||||
user_session_token(headers),
|
||||
)
|
||||
.await
|
||||
.unwrap_or(false)
|
||||
} else {
|
||||
false
|
||||
};
|
||||
@@ -433,7 +425,10 @@ async fn save_editor_settings(
|
||||
}
|
||||
|
||||
let preferences = if wants_personal_update {
|
||||
let user_id = user.as_ref().expect("personal preferences require a user").id;
|
||||
let user_id = user
|
||||
.as_ref()
|
||||
.expect("personal preferences require a user")
|
||||
.id;
|
||||
let mut preferences = db::load_editor_preferences(&state.db, user_id, resource)
|
||||
.await?
|
||||
.unwrap_or_default();
|
||||
@@ -467,12 +462,8 @@ async fn save_editor_settings(
|
||||
};
|
||||
|
||||
let resource_settings = if wants_global_update {
|
||||
let mut settings = db::load_resource_editor_settings(
|
||||
&state.db,
|
||||
settings_kind,
|
||||
settings_slug,
|
||||
)
|
||||
.await?;
|
||||
let mut settings =
|
||||
db::load_resource_editor_settings(&state.db, settings_kind, settings_slug).await?;
|
||||
if let Some(mode) = payload.authorship_mode {
|
||||
settings.authorship_mode = match mode.as_str() {
|
||||
"simple" => "simple".into(),
|
||||
@@ -567,7 +558,12 @@ pub async fn open_workspace(
|
||||
&state,
|
||||
&workspace_slug,
|
||||
payload.password.as_deref(),
|
||||
resource_request_token(&headers, "workspace", &workspace_slug, payload.access_token.as_deref()),
|
||||
resource_request_token(
|
||||
&headers,
|
||||
"workspace",
|
||||
&workspace_slug,
|
||||
payload.access_token.as_deref(),
|
||||
),
|
||||
bearer_token(&headers),
|
||||
&headers,
|
||||
)
|
||||
@@ -614,7 +610,12 @@ pub async fn create_note(
|
||||
&state,
|
||||
&workspace_slug,
|
||||
payload.password.as_deref(),
|
||||
resource_request_token(&headers, "workspace", &workspace_slug, payload.access_token.as_deref()),
|
||||
resource_request_token(
|
||||
&headers,
|
||||
"workspace",
|
||||
&workspace_slug,
|
||||
payload.access_token.as_deref(),
|
||||
),
|
||||
bearer_token(&headers),
|
||||
&headers,
|
||||
)
|
||||
@@ -628,13 +629,19 @@ pub async fn create_note(
|
||||
&state,
|
||||
"workspace",
|
||||
&workspace_slug,
|
||||
resource_request_token(&headers, "workspace", &workspace_slug, payload.access_token.as_deref()),
|
||||
resource_request_token(
|
||||
&headers,
|
||||
"workspace",
|
||||
&workspace_slug,
|
||||
payload.access_token.as_deref(),
|
||||
),
|
||||
bearer_token(&headers),
|
||||
)
|
||||
.await?
|
||||
};
|
||||
require_write(level)?;
|
||||
let title = validate_name(&payload.name, "Note name")?;
|
||||
let initial_content = validate_initial_content(payload.content.as_deref())?;
|
||||
let base = slugify(title);
|
||||
if base.is_empty() {
|
||||
return Err(ApiError::bad_request(
|
||||
@@ -670,6 +677,17 @@ pub async fn create_note(
|
||||
created_by_guest_id.as_deref(),
|
||||
)
|
||||
.await?;
|
||||
if let Some(content) = initial_content {
|
||||
db::save_revision(
|
||||
&state.db,
|
||||
note.id,
|
||||
workspace.id,
|
||||
content,
|
||||
created_by.as_deref(),
|
||||
"[]",
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
Ok((
|
||||
StatusCode::CREATED,
|
||||
Json(NoteListItem {
|
||||
@@ -865,9 +883,8 @@ pub async fn set_note_editor_settings(
|
||||
let note = db::find_note(&state.db, workspace.id, ¬e_slug)
|
||||
.await?
|
||||
.ok_or_else(ApiError::not_found_note)?;
|
||||
let creator_can_manage_authorship =
|
||||
note_creator_is_requester(&state, &headers, ¬e).await?
|
||||
|| has_password_write_access(&state, &headers, "workspace", &workspace_slug).await?;
|
||||
let creator_can_manage_authorship = note_creator_is_requester(&state, &headers, ¬e).await?
|
||||
|| has_password_write_access(&state, &headers, "workspace", &workspace_slug).await?;
|
||||
save_editor_settings(
|
||||
&state,
|
||||
&headers,
|
||||
@@ -893,7 +910,12 @@ pub async fn history(
|
||||
&workspace_slug,
|
||||
¬e_slug,
|
||||
payload.password.as_deref(),
|
||||
resource_request_token(&headers, "workspace", &workspace_slug, payload.access_token.as_deref()),
|
||||
resource_request_token(
|
||||
&headers,
|
||||
"workspace",
|
||||
&workspace_slug,
|
||||
payload.access_token.as_deref(),
|
||||
),
|
||||
bearer_token(&headers),
|
||||
&headers,
|
||||
)
|
||||
@@ -921,7 +943,12 @@ pub async fn restore(
|
||||
&workspace_slug,
|
||||
¬e_slug,
|
||||
payload.password.as_deref(),
|
||||
resource_request_token(&headers, "workspace", &workspace_slug, payload.access_token.as_deref()),
|
||||
resource_request_token(
|
||||
&headers,
|
||||
"workspace",
|
||||
&workspace_slug,
|
||||
payload.access_token.as_deref(),
|
||||
),
|
||||
bearer_token(&headers),
|
||||
&headers,
|
||||
)
|
||||
@@ -935,7 +962,12 @@ pub async fn restore(
|
||||
&state,
|
||||
"workspace",
|
||||
&workspace_slug,
|
||||
resource_request_token(&headers, "workspace", &workspace_slug, payload.access_token.as_deref()),
|
||||
resource_request_token(
|
||||
&headers,
|
||||
"workspace",
|
||||
&workspace_slug,
|
||||
payload.access_token.as_deref(),
|
||||
),
|
||||
bearer_token(&headers),
|
||||
)
|
||||
.await?
|
||||
@@ -1238,6 +1270,16 @@ fn validate_password(password: Option<&str>) -> Result<Option<&str>, ApiError> {
|
||||
Ok(Some(password))
|
||||
}
|
||||
|
||||
fn validate_initial_content(content: Option<&str>) -> Result<Option<&str>, ApiError> {
|
||||
let Some(content) = content.filter(|value| !value.is_empty()) else {
|
||||
return Ok(None);
|
||||
};
|
||||
if content.len() > MAX_DOCUMENT_SIZE_BYTES {
|
||||
return Err(ApiError::bad_request("The document is too large"));
|
||||
}
|
||||
Ok(Some(content))
|
||||
}
|
||||
|
||||
async fn unique_workspace_slug(state: &SharedState, title: &str) -> Result<String, ApiError> {
|
||||
let base = slugify(title);
|
||||
if base.is_empty() {
|
||||
|
||||
Reference in New Issue
Block a user