new functions and js v=
This commit is contained in:
+12
-42
@@ -8,7 +8,7 @@ use axum::{
|
||||
use tower::{ServiceBuilder, service_fn};
|
||||
use tower_http::{services::ServeDir, set_header::SetResponseHeaderLayer, trace::TraceLayer};
|
||||
|
||||
use crate::{api, auth, db, state::SharedState, websocket};
|
||||
use crate::{api, assets, auth, db, state::SharedState, websocket};
|
||||
use std::convert::Infallible;
|
||||
|
||||
pub fn router(
|
||||
@@ -176,12 +176,13 @@ async fn health() -> &'static str {
|
||||
}
|
||||
|
||||
async fn home(State(state): State<SharedState>) -> Response {
|
||||
versioned_html(
|
||||
assets::render_html(
|
||||
include_str!("../static/home.html"),
|
||||
&state.asset_version,
|
||||
state.registration_enabled,
|
||||
&state.frontend_log_level,
|
||||
state.upload_max_size_bytes,
|
||||
"home",
|
||||
)
|
||||
}
|
||||
|
||||
@@ -190,12 +191,13 @@ async fn pad(State(state): State<SharedState>, Path(slug): Path<String>) -> Resp
|
||||
Ok(Some(pad)) => {
|
||||
let html = include_str!("../static/pad.html")
|
||||
.replace("__PAD_TITLE__", &escape_html(&pad.title));
|
||||
versioned_html(
|
||||
assets::render_html(
|
||||
&html,
|
||||
&state.asset_version,
|
||||
state.registration_enabled,
|
||||
&state.frontend_log_level,
|
||||
state.upload_max_size_bytes,
|
||||
"pad",
|
||||
)
|
||||
}
|
||||
Ok(None) => error_response(
|
||||
@@ -216,12 +218,13 @@ async fn pad(State(state): State<SharedState>, Path(slug): Path<String>) -> Resp
|
||||
|
||||
async fn public_page(State(state): State<SharedState>, Path(token): Path<String>) -> Response {
|
||||
match db::find_published_page(&state.db, &token).await {
|
||||
Ok(Some(_)) => versioned_html(
|
||||
Ok(Some(_)) => assets::render_html(
|
||||
include_str!("../static/public.html"),
|
||||
&state.asset_version,
|
||||
state.registration_enabled,
|
||||
&state.frontend_log_level,
|
||||
state.upload_max_size_bytes,
|
||||
"public",
|
||||
),
|
||||
Ok(None) => error_response(
|
||||
StatusCode::NOT_FOUND,
|
||||
@@ -247,12 +250,13 @@ async fn workspace(
|
||||
Ok(Some(workspace)) => {
|
||||
let html = include_str!("../static/workspace.html")
|
||||
.replace("__WORKSPACE_TITLE__", &escape_html(&workspace.title));
|
||||
versioned_html(
|
||||
assets::render_html(
|
||||
&html,
|
||||
&state.asset_version,
|
||||
state.registration_enabled,
|
||||
&state.frontend_log_level,
|
||||
state.upload_max_size_bytes,
|
||||
"workspace",
|
||||
)
|
||||
}
|
||||
Ok(None) => error_response(
|
||||
@@ -300,12 +304,13 @@ async fn note(
|
||||
.replace("__NOTE_TITLE__", &escape_html(¬e.title))
|
||||
.replace("__WORKSPACE_TITLE__", &escape_html(&workspace.title))
|
||||
.replace("__WORKSPACE_SLUG__", &escape_html(&workspace_slug));
|
||||
versioned_html(
|
||||
assets::render_html(
|
||||
&html,
|
||||
&state.asset_version,
|
||||
state.registration_enabled,
|
||||
&state.frontend_log_level,
|
||||
state.upload_max_size_bytes,
|
||||
"note",
|
||||
)
|
||||
}
|
||||
Ok(None) => error_response(
|
||||
@@ -382,7 +387,7 @@ fn error_response(
|
||||
asset_version: &str,
|
||||
) -> Response {
|
||||
let html = include_str!("../static/error.html")
|
||||
.replace("__ASSET_VERSION__", &escape_html(asset_version))
|
||||
.replace("__APP_STYLESHEET__", &assets::stylesheet_tag(asset_version, "styles"))
|
||||
.replace("__ERROR_CODE__", &escape_html(code))
|
||||
.replace("__ERROR_TITLE__", &escape_html(title))
|
||||
.replace("__ERROR_MESSAGE__", &escape_html(message))
|
||||
@@ -394,41 +399,6 @@ fn error_response(
|
||||
response
|
||||
}
|
||||
|
||||
fn versioned_html(
|
||||
template: &str,
|
||||
asset_version: &str,
|
||||
registration_enabled: bool,
|
||||
frontend_log_level: &str,
|
||||
upload_max_size_bytes: usize,
|
||||
) -> Response {
|
||||
let frontend_config = format!(
|
||||
r#"<script>window.__RUSTPAD_CONFIG__=Object.freeze({{frontendLogLevel:"{}",uploadMaxSizeBytes:{}}});</script>"#,
|
||||
escape_js_string(frontend_log_level),
|
||||
upload_max_size_bytes,
|
||||
);
|
||||
let html = template
|
||||
.replace("__ASSET_VERSION__", asset_version)
|
||||
.replace(
|
||||
"__REGISTRATION_ENABLED__",
|
||||
if registration_enabled {
|
||||
"true"
|
||||
} else {
|
||||
"false"
|
||||
},
|
||||
)
|
||||
.replace("</head>", &format!("{frontend_config}</head>"));
|
||||
let mut response = Html(html).into_response();
|
||||
no_store(&mut response);
|
||||
response
|
||||
}
|
||||
|
||||
fn escape_js_string(value: &str) -> String {
|
||||
value
|
||||
.replace('\\', "\\\\")
|
||||
.replace('"', "\\\"")
|
||||
.replace('<', "\\u003c")
|
||||
}
|
||||
|
||||
fn no_store(response: &mut Response) {
|
||||
response.headers_mut().insert(
|
||||
header::CACHE_CONTROL,
|
||||
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
use axum::{
|
||||
http::{HeaderValue, header},
|
||||
response::{Html, IntoResponse, Response},
|
||||
};
|
||||
|
||||
const MODULES: &[&str] = &[
|
||||
"api",
|
||||
"auth-ui",
|
||||
"clipboard",
|
||||
"editor-format",
|
||||
"emoji-data",
|
||||
"emoji-picker",
|
||||
"image-upload",
|
||||
"logger",
|
||||
"markdown",
|
||||
"modal",
|
||||
"session",
|
||||
"socket",
|
||||
"url-state",
|
||||
];
|
||||
|
||||
pub fn render_html(
|
||||
template: &str,
|
||||
asset_version: &str,
|
||||
registration_enabled: bool,
|
||||
frontend_log_level: &str,
|
||||
upload_max_size_bytes: usize,
|
||||
entrypoint: &str,
|
||||
) -> Response {
|
||||
let urls = AssetUrls::new(asset_version);
|
||||
let frontend_config = frontend_config(frontend_log_level, upload_max_size_bytes);
|
||||
let html = template
|
||||
.replace("__APP_STYLESHEET__", &urls.stylesheet("styles"))
|
||||
.replace("__APP_IMPORT_MAP__", &urls.import_map())
|
||||
.replace("__APP_ENTRYPOINT__", &urls.entrypoint(entrypoint))
|
||||
.replace(
|
||||
"__REGISTRATION_ENABLED__",
|
||||
if registration_enabled {
|
||||
"true"
|
||||
} else {
|
||||
"false"
|
||||
},
|
||||
)
|
||||
.replace("</head>", &format!("{frontend_config}</head>"));
|
||||
|
||||
let mut response = Html(html).into_response();
|
||||
response.headers_mut().insert(
|
||||
header::CACHE_CONTROL,
|
||||
HeaderValue::from_static("private, no-store"),
|
||||
);
|
||||
response
|
||||
}
|
||||
|
||||
pub fn stylesheet_tag(asset_version: &str, name: &str) -> String {
|
||||
AssetUrls::new(asset_version).stylesheet(name)
|
||||
}
|
||||
|
||||
fn frontend_config(frontend_log_level: &str, upload_max_size_bytes: usize) -> String {
|
||||
format!(
|
||||
r#"<script>window.__RUSTPAD_CONFIG__=Object.freeze({{frontendLogLevel:"{}",uploadMaxSizeBytes:{}}});</script>"#,
|
||||
escape_js_string(frontend_log_level),
|
||||
upload_max_size_bytes,
|
||||
)
|
||||
}
|
||||
|
||||
struct AssetUrls<'a> {
|
||||
version: &'a str,
|
||||
}
|
||||
|
||||
impl<'a> AssetUrls<'a> {
|
||||
fn new(version: &'a str) -> Self {
|
||||
Self { version }
|
||||
}
|
||||
|
||||
fn url(&self, path: &str) -> String {
|
||||
format!("/assets/{path}?v={}", self.version)
|
||||
}
|
||||
|
||||
fn stylesheet(&self, name: &str) -> String {
|
||||
format!(
|
||||
r#"<link rel="stylesheet" href="{}">"#,
|
||||
self.url(&format!("css/{name}.css"))
|
||||
)
|
||||
}
|
||||
|
||||
fn entrypoint(&self, name: &str) -> String {
|
||||
format!(
|
||||
r#"<script type="module" src="{}"></script>"#,
|
||||
self.url(&format!("js/{name}.js"))
|
||||
)
|
||||
}
|
||||
|
||||
fn import_map(&self) -> String {
|
||||
let imports = MODULES
|
||||
.iter()
|
||||
.map(|module| {
|
||||
format!(
|
||||
r#""@rustpad/{module}":"{}""#,
|
||||
self.url(&format!("js/{module}.js"))
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join(",");
|
||||
|
||||
format!(r#"<script type="importmap">{{"imports":{{{imports}}}}}</script>"#)
|
||||
}
|
||||
}
|
||||
|
||||
fn escape_js_string(value: &str) -> String {
|
||||
value
|
||||
.replace('\\', "\\\\")
|
||||
.replace('"', "\\\"")
|
||||
.replace('<', "\\u003c")
|
||||
}
|
||||
+5
-5
@@ -873,10 +873,9 @@ pub async fn resource_sharing(
|
||||
.fetch_all(state.db.pool())
|
||||
.await
|
||||
.map_err(AuthError::database)?;
|
||||
let links: Vec<(String, String, Option<String>, String)> = sqlx::query_as(queries::get(
|
||||
state.db.kind(),
|
||||
queries::RESOURCE_SHARING_LINKS,
|
||||
))
|
||||
let links: Vec<(String, Option<String>, String, Option<String>, String)> = sqlx::query_as(
|
||||
queries::get(state.db.kind(), queries::RESOURCE_SHARING_LINKS),
|
||||
)
|
||||
.bind(kind)
|
||||
.bind(slug)
|
||||
.fetch_all(state.db.pool())
|
||||
@@ -892,7 +891,7 @@ pub async fn resource_sharing(
|
||||
.await
|
||||
.map_err(AuthError::database)?;
|
||||
Ok(Json(
|
||||
serde_json::json!({"users":users.into_iter().map(|(email,nickname,permission)|serde_json::json!({"email":email,"nickname":nickname,"permission":permission})).collect::<Vec<_>>(), "pending":pending.into_iter().map(|(email,nickname,permission,expires_at)|serde_json::json!({"email":email,"nickname":nickname,"permission":permission,"expires_at":expires_at})).collect::<Vec<_>>(), "links":links.into_iter().map(|(token,permission,expires_at,created_at)|serde_json::json!({"token":token,"permission":permission,"expires_at":expires_at,"created_at":created_at})).collect::<Vec<_>>() }),
|
||||
serde_json::json!({"users":users.into_iter().map(|(email,nickname,permission)|serde_json::json!({"email":email,"nickname":nickname,"permission":permission})).collect::<Vec<_>>(), "pending":pending.into_iter().map(|(email,nickname,permission,expires_at)|serde_json::json!({"email":email,"nickname":nickname,"permission":permission,"expires_at":expires_at})).collect::<Vec<_>>(), "links":links.into_iter().map(|(token_hash,token,permission,expires_at,created_at)|serde_json::json!({"token_hash":token_hash,"token":token,"permission":permission,"expires_at":expires_at,"created_at":created_at})).collect::<Vec<_>>() }),
|
||||
))
|
||||
}
|
||||
|
||||
@@ -909,6 +908,7 @@ pub async fn create_share_link(
|
||||
let token_hash = hash_token(&token);
|
||||
sqlx::query(queries::get(state.db.kind(), queries::SHARE_LINK_INSERT))
|
||||
.bind(token_hash)
|
||||
.bind(&token)
|
||||
.bind(&req.kind)
|
||||
.bind(req.slug.trim())
|
||||
.bind(permission)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
mod api;
|
||||
mod app;
|
||||
mod assets;
|
||||
mod auth;
|
||||
mod config;
|
||||
mod database;
|
||||
|
||||
+17
-34
@@ -28,8 +28,7 @@ pub const AUTH_FIND_CONFIRMATION_TOKEN: &str =
|
||||
"SELECT user_id, expires_at, used_at FROM account_confirmation_tokens WHERE token = ?";
|
||||
pub const AUTH_CONFIRM_USER: &str =
|
||||
"UPDATE users SET confirmed_at = ?, updated_at = ? WHERE id = ?";
|
||||
pub const AUTH_CONSUME_CONFIRMATION_TOKEN: &str =
|
||||
"UPDATE account_confirmation_tokens SET used_at = ? WHERE token = ? AND used_at IS NULL AND expires_at > ?";
|
||||
pub const AUTH_CONSUME_CONFIRMATION_TOKEN: &str = "UPDATE account_confirmation_tokens SET used_at = ? WHERE token = ? AND used_at IS NULL AND expires_at > ?";
|
||||
pub const AUTH_DELETE_RESET_TOKENS_BY_USER: &str =
|
||||
"DELETE FROM password_reset_tokens WHERE user_id = ?";
|
||||
pub const AUTH_INSERT_RESET_TOKEN: &str =
|
||||
@@ -38,8 +37,7 @@ pub const AUTH_FIND_RESET_TOKEN: &str =
|
||||
"SELECT user_id, expires_at, used_at FROM password_reset_tokens WHERE token = ?";
|
||||
pub const AUTH_UPDATE_PASSWORD: &str =
|
||||
"UPDATE users SET password_hash = ?, updated_at = ? WHERE id = ?";
|
||||
pub const AUTH_CONSUME_RESET_TOKEN: &str =
|
||||
"UPDATE password_reset_tokens SET used_at = ? WHERE token = ? AND used_at IS NULL AND expires_at > ?";
|
||||
pub const AUTH_CONSUME_RESET_TOKEN: &str = "UPDATE password_reset_tokens SET used_at = ? WHERE token = ? AND used_at IS NULL AND expires_at > ?";
|
||||
pub const AUTH_DELETE_SESSIONS_BY_USER: &str = "DELETE FROM user_sessions WHERE user_id = ?";
|
||||
pub const AUTH_USER_BY_SESSION: &str = "SELECT u.id, u.nickname, u.email, u.password_hash, u.confirmed_at FROM user_sessions s JOIN users u ON u.id = s.user_id WHERE s.token = ? AND s.expires_at > ?";
|
||||
pub const AUTH_INSERT_SESSION: &str =
|
||||
@@ -67,40 +65,25 @@ pub const USER_SET_PAD_PRIVACY: &str =
|
||||
"UPDATE pads SET is_private = ?, updated_at = CURRENT_TIMESTAMP WHERE slug = ?";
|
||||
pub const RESOURCE_ACCESS_TOKENS_DELETE_BY_RESOURCE: &str =
|
||||
"DELETE FROM resource_access_tokens WHERE resource_kind = ? AND resource_slug = ?";
|
||||
pub const RESOURCE_ACCESS_TOKENS_INSERT: &str =
|
||||
"INSERT INTO resource_access_tokens (token_hash, resource_kind, resource_slug, expires_at) VALUES (?, ?, ?, ?)";
|
||||
pub const RESOURCE_ACCESS_TOKENS_VALID_COUNT: &str =
|
||||
"SELECT COUNT(*) FROM resource_access_tokens WHERE token_hash = ? AND resource_kind = ? AND resource_slug = ? AND expires_at > ?";
|
||||
pub const RESOURCE_PERMISSION_DELETE_USER: &str =
|
||||
"DELETE FROM resource_permissions WHERE resource_kind = ? AND resource_slug = ? AND user_id = ?";
|
||||
pub const RESOURCE_PERMISSION_INSERT: &str =
|
||||
"INSERT INTO resource_permissions (resource_kind, resource_slug, user_id, permission) VALUES (?, ?, ?, ?)";
|
||||
pub const SHARE_INVITATION_DELETE_USER: &str =
|
||||
"DELETE FROM resource_share_invitations WHERE resource_kind = ? AND resource_slug = ? AND user_id = ?";
|
||||
pub const SHARE_INVITATION_INSERT: &str =
|
||||
"INSERT INTO resource_share_invitations (token_hash, resource_kind, resource_slug, user_id, permission, created_by, expires_at) VALUES (?, ?, ?, ?, ?, ?, ?)";
|
||||
pub const RESOURCE_ACCESS_TOKENS_INSERT: &str = "INSERT INTO resource_access_tokens (token_hash, resource_kind, resource_slug, expires_at) VALUES (?, ?, ?, ?)";
|
||||
pub const RESOURCE_ACCESS_TOKENS_VALID_COUNT: &str = "SELECT COUNT(*) FROM resource_access_tokens WHERE token_hash = ? AND resource_kind = ? AND resource_slug = ? AND expires_at > ?";
|
||||
pub const RESOURCE_PERMISSION_DELETE_USER: &str = "DELETE FROM resource_permissions WHERE resource_kind = ? AND resource_slug = ? AND user_id = ?";
|
||||
pub const RESOURCE_PERMISSION_INSERT: &str = "INSERT INTO resource_permissions (resource_kind, resource_slug, user_id, permission) VALUES (?, ?, ?, ?)";
|
||||
pub const SHARE_INVITATION_DELETE_USER: &str = "DELETE FROM resource_share_invitations WHERE resource_kind = ? AND resource_slug = ? AND user_id = ?";
|
||||
pub const SHARE_INVITATION_INSERT: &str = "INSERT INTO resource_share_invitations (token_hash, resource_kind, resource_slug, user_id, permission, created_by, expires_at) VALUES (?, ?, ?, ?, ?, ?, ?)";
|
||||
pub const SHARE_INVITATION_DELETE_TOKEN: &str =
|
||||
"DELETE FROM resource_share_invitations WHERE token_hash = ?";
|
||||
pub const SHARE_INVITATION_FIND_TOKEN: &str =
|
||||
"SELECT resource_kind, resource_slug, user_id, permission, expires_at, accepted_at FROM resource_share_invitations WHERE token_hash = ?";
|
||||
pub const SHARE_INVITATION_FIND_TOKEN: &str = "SELECT resource_kind, resource_slug, user_id, permission, expires_at, accepted_at FROM resource_share_invitations WHERE token_hash = ?";
|
||||
pub const SHARE_INVITATION_ACCEPT: &str =
|
||||
"UPDATE resource_share_invitations SET accepted_at = ? WHERE token_hash = ?";
|
||||
pub const RESOURCE_SHARING_USERS: &str =
|
||||
"SELECT u.email, u.nickname, rp.permission FROM resource_permissions rp JOIN users u ON u.id = rp.user_id WHERE rp.resource_kind = ? AND rp.resource_slug = ? ORDER BY u.email";
|
||||
pub const RESOURCE_SHARING_LINKS: &str =
|
||||
"SELECT token_hash, permission, expires_at, created_at FROM resource_share_links WHERE resource_kind = ? AND resource_slug = ? AND revoked_at IS NULL ORDER BY created_at DESC";
|
||||
pub const RESOURCE_SHARING_PENDING: &str =
|
||||
"SELECT u.email, u.nickname, i.permission, i.expires_at FROM resource_share_invitations i JOIN users u ON u.id = i.user_id WHERE i.resource_kind = ? AND i.resource_slug = ? AND i.accepted_at IS NULL ORDER BY u.email";
|
||||
pub const SHARE_LINK_INSERT: &str =
|
||||
"INSERT INTO resource_share_links (token_hash, resource_kind, resource_slug, permission, expires_at, created_by) VALUES (?, ?, ?, ?, ?, ?)";
|
||||
pub const SHARE_LINK_UPDATE: &str =
|
||||
"UPDATE resource_share_links SET permission = ?, expires_at = ? WHERE token_hash = ? AND resource_kind = ? AND resource_slug = ? AND revoked_at IS NULL";
|
||||
pub const SHARE_LINK_REVOKE: &str =
|
||||
"UPDATE resource_share_links SET revoked_at = ? WHERE token_hash = ? AND resource_kind = ? AND resource_slug = ?";
|
||||
pub const RESOURCE_PERMISSION_BY_USER: &str =
|
||||
"SELECT permission FROM resource_permissions WHERE resource_kind = ? AND resource_slug = ? AND user_id = ?";
|
||||
pub const SHARE_LINK_PERMISSION: &str =
|
||||
"SELECT permission FROM resource_share_links WHERE token_hash = ? AND resource_kind = ? AND resource_slug = ? AND revoked_at IS NULL AND (expires_at IS NULL OR expires_at > ?)";
|
||||
pub const RESOURCE_SHARING_USERS: &str = "SELECT u.email, u.nickname, rp.permission FROM resource_permissions rp JOIN users u ON u.id = rp.user_id WHERE rp.resource_kind = ? AND rp.resource_slug = ? ORDER BY u.email";
|
||||
pub const RESOURCE_SHARING_LINKS: &str = "SELECT token_hash, token, permission, expires_at, created_at FROM resource_share_links WHERE resource_kind = ? AND resource_slug = ? AND revoked_at IS NULL ORDER BY created_at DESC";
|
||||
pub const RESOURCE_SHARING_PENDING: &str = "SELECT u.email, u.nickname, i.permission, i.expires_at FROM resource_share_invitations i JOIN users u ON u.id = i.user_id WHERE i.resource_kind = ? AND i.resource_slug = ? AND i.accepted_at IS NULL ORDER BY u.email";
|
||||
pub const SHARE_LINK_INSERT: &str = "INSERT INTO resource_share_links (token_hash, token, resource_kind, resource_slug, permission, expires_at, created_by) VALUES (?, ?, ?, ?, ?, ?, ?)";
|
||||
pub const SHARE_LINK_UPDATE: &str = "UPDATE resource_share_links SET permission = ?, expires_at = ? WHERE token_hash = ? AND resource_kind = ? AND resource_slug = ? AND revoked_at IS NULL";
|
||||
pub const SHARE_LINK_REVOKE: &str = "UPDATE resource_share_links SET revoked_at = ? WHERE token_hash = ? AND resource_kind = ? AND resource_slug = ?";
|
||||
pub const RESOURCE_PERMISSION_BY_USER: &str = "SELECT permission FROM resource_permissions WHERE resource_kind = ? AND resource_slug = ? AND user_id = ?";
|
||||
pub const SHARE_LINK_PERMISSION: &str = "SELECT permission FROM resource_share_links WHERE token_hash = ? AND resource_kind = ? AND resource_slug = ? AND revoked_at IS NULL AND (expires_at IS NULL OR expires_at > ?)";
|
||||
|
||||
pub const Q001: &str = "SELECT id, slug, title, password_hash, created_at, updated_at, CAST(CASE WHEN is_private THEN 1 ELSE 0 END AS BIGINT) AS is_private FROM workspaces WHERE slug = ?";
|
||||
pub const Q002: &str = "INSERT INTO workspaces (slug, title, password_hash) VALUES (?, ?, ?)";
|
||||
|
||||
+76
-52
@@ -22,6 +22,7 @@ enum ClientMessage {
|
||||
access_token: Option<String>,
|
||||
nickname: Option<String>,
|
||||
session_token: Option<String>,
|
||||
guest_id: Option<String>,
|
||||
color: Option<String>,
|
||||
},
|
||||
Update {
|
||||
@@ -103,28 +104,31 @@ async fn handle_socket(
|
||||
let _ = send_error(&mut socket, "Note not found").await;
|
||||
return;
|
||||
};
|
||||
let (password, access_token, nickname, session_token, color) = match socket.recv().await {
|
||||
Some(Ok(Message::Text(text))) => match serde_json::from_str::<ClientMessage>(&text) {
|
||||
Ok(ClientMessage::Authenticate {
|
||||
password,
|
||||
access_token,
|
||||
nickname,
|
||||
session_token,
|
||||
color,
|
||||
}) => (
|
||||
password,
|
||||
access_token,
|
||||
clean_nickname(nickname),
|
||||
session_token,
|
||||
clean_color(color),
|
||||
),
|
||||
_ => {
|
||||
let _ = send_error(&mut socket, "Wymagane uwierzytelnienie").await;
|
||||
return;
|
||||
}
|
||||
},
|
||||
_ => return,
|
||||
};
|
||||
let (password, access_token, nickname, session_token, guest_id, color) =
|
||||
match socket.recv().await {
|
||||
Some(Ok(Message::Text(text))) => match serde_json::from_str::<ClientMessage>(&text) {
|
||||
Ok(ClientMessage::Authenticate {
|
||||
password,
|
||||
access_token,
|
||||
nickname,
|
||||
session_token,
|
||||
guest_id,
|
||||
color,
|
||||
}) => (
|
||||
password,
|
||||
access_token,
|
||||
clean_nickname(nickname),
|
||||
session_token,
|
||||
clean_guest_id(guest_id),
|
||||
clean_color(color),
|
||||
),
|
||||
_ => {
|
||||
let _ = send_error(&mut socket, "Wymagane uwierzytelnienie").await;
|
||||
return;
|
||||
}
|
||||
},
|
||||
_ => return,
|
||||
};
|
||||
let nickname = match auth::authorize_nickname(&state, nickname, session_token.clone()).await {
|
||||
Ok(value) => value,
|
||||
Err(message) => {
|
||||
@@ -138,7 +142,11 @@ async fn handle_socket(
|
||||
.ok()
|
||||
.flatten()
|
||||
.map(|user| format!("user:{}", user.id)),
|
||||
None => None,
|
||||
None => guest_id.as_ref().and_then(|id| {
|
||||
nickname
|
||||
.as_ref()
|
||||
.map(|name| format!("guest:{id}:{}", name.to_lowercase()))
|
||||
}),
|
||||
};
|
||||
let permission = auth::resource_permission(
|
||||
&state,
|
||||
@@ -234,6 +242,15 @@ fn clean_nickname(value: Option<String>) -> Option<String> {
|
||||
.map(|v| v.trim().chars().take(40).collect::<String>())
|
||||
.filter(|v| !v.is_empty())
|
||||
}
|
||||
fn clean_guest_id(value: Option<String>) -> Option<String> {
|
||||
value
|
||||
.map(|v| v.trim().chars().take(64).collect::<String>())
|
||||
.filter(|v| {
|
||||
v.len() >= 16
|
||||
&& v.chars()
|
||||
.all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_')
|
||||
})
|
||||
}
|
||||
fn clean_color(value: Option<String>) -> Option<String> {
|
||||
value.map(|v| v.trim().to_ascii_lowercase()).filter(|v| {
|
||||
v.len() == 7 && v.starts_with('#') && v[1..].chars().all(|c| c.is_ascii_hexdigit())
|
||||
@@ -331,34 +348,37 @@ async fn handle_pad_socket(mut socket: WebSocket, state: SharedState, slug: Stri
|
||||
.await;
|
||||
return;
|
||||
};
|
||||
let (password, access_token, nickname, session_token, color) = match socket.recv().await {
|
||||
Some(Ok(Message::Text(text))) => match serde_json::from_str::<ClientMessage>(&text) {
|
||||
Ok(ClientMessage::Authenticate {
|
||||
password,
|
||||
access_token,
|
||||
nickname,
|
||||
session_token,
|
||||
color,
|
||||
}) => (
|
||||
password,
|
||||
access_token,
|
||||
clean_nickname(nickname),
|
||||
session_token,
|
||||
clean_color(color),
|
||||
),
|
||||
_ => {
|
||||
let _ = send_pad(
|
||||
&mut socket,
|
||||
&PadServerMessage::Error {
|
||||
message: "Wymagane uwierzytelnienie".into(),
|
||||
},
|
||||
)
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
},
|
||||
_ => return,
|
||||
};
|
||||
let (password, access_token, nickname, session_token, guest_id, color) =
|
||||
match socket.recv().await {
|
||||
Some(Ok(Message::Text(text))) => match serde_json::from_str::<ClientMessage>(&text) {
|
||||
Ok(ClientMessage::Authenticate {
|
||||
password,
|
||||
access_token,
|
||||
nickname,
|
||||
session_token,
|
||||
guest_id,
|
||||
color,
|
||||
}) => (
|
||||
password,
|
||||
access_token,
|
||||
clean_nickname(nickname),
|
||||
session_token,
|
||||
clean_guest_id(guest_id),
|
||||
clean_color(color),
|
||||
),
|
||||
_ => {
|
||||
let _ = send_pad(
|
||||
&mut socket,
|
||||
&PadServerMessage::Error {
|
||||
message: "Wymagane uwierzytelnienie".into(),
|
||||
},
|
||||
)
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
},
|
||||
_ => return,
|
||||
};
|
||||
let nickname = match auth::authorize_nickname(&state, nickname, session_token.clone()).await {
|
||||
Ok(value) => value,
|
||||
Err(message) => {
|
||||
@@ -372,7 +392,11 @@ async fn handle_pad_socket(mut socket: WebSocket, state: SharedState, slug: Stri
|
||||
.ok()
|
||||
.flatten()
|
||||
.map(|user| format!("user:{}", user.id)),
|
||||
None => None,
|
||||
None => guest_id.as_ref().and_then(|id| {
|
||||
nickname
|
||||
.as_ref()
|
||||
.map(|name| format!("guest:{id}:{}", name.to_lowercase()))
|
||||
}),
|
||||
};
|
||||
let permission = auth::resource_permission(
|
||||
&state,
|
||||
|
||||
Reference in New Issue
Block a user