add: option_source_password and option_view_password
This commit is contained in:
parent
a49efdd238
commit
2de840f50d
5 changed files with 158 additions and 13 deletions
|
@ -180,6 +180,11 @@ video {
|
|||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.card-nest .card:nth-child(1) {
|
||||
background: var(--color-super-raised);
|
||||
padding: var(--pad-2) var(--pad-4);
|
||||
}
|
||||
|
||||
/* button */
|
||||
.button {
|
||||
--h: 36px;
|
||||
|
@ -307,6 +312,10 @@ input[data-invalid] {
|
|||
border-left: inset 5px var(--color-red);
|
||||
}
|
||||
|
||||
input.surface {
|
||||
background: var(--color-surface);
|
||||
}
|
||||
|
||||
/* typo */
|
||||
p {
|
||||
margin-bottom: var(--pad-4);
|
||||
|
|
32
app/templates_src/password.lisp
Normal file
32
app/templates_src/password.lisp
Normal file
|
@ -0,0 +1,32 @@
|
|||
(text "{% extends \"root.lisp\" %} {% block head %}")
|
||||
(title
|
||||
(text "{{ entry.slug }}"))
|
||||
(link ("rel" "icon") ("href" "/public/favicon.svg"))
|
||||
(text "{% endblock %} {% block body %}")
|
||||
(main
|
||||
("class" "card-nest")
|
||||
(div
|
||||
("class" "card flex items-center gap-2 no_fill")
|
||||
(text "{{ icon \"lock\" }}")
|
||||
(b (text "Password required")))
|
||||
(form
|
||||
("class" "card flex flex-col gap-2")
|
||||
("onsubmit" "use_password(event)")
|
||||
(div
|
||||
("class" "flex flex-collapse gap-2")
|
||||
(input
|
||||
("class" "surface")
|
||||
("required" "")
|
||||
("placeholder" "Password")
|
||||
("name" "password"))
|
||||
(button
|
||||
("class" "button surface")
|
||||
(text "Go")))))
|
||||
(script
|
||||
(text "async function use_password(event) {
|
||||
event.preventDefault();
|
||||
const hash = Array.from(new Uint8Array(await window.crypto.subtle.digest(\"SHA-256\", new TextEncoder().encode(event.target.password.value))));
|
||||
const hex_hash = hash.map((b) => b.toString(16).padStart(2, \"0\")).join(\"\");
|
||||
window.location.href = `?key=h:${hex_hash}`;
|
||||
}"))
|
||||
(text "{% endblock %}")
|
|
@ -24,7 +24,7 @@
|
|||
("class" "w-full flex justify-between gap-2")
|
||||
(a
|
||||
("class" "button")
|
||||
("href" "/{{ entry.slug }}/edit")
|
||||
("href" "/{{ entry.slug }}/edit{% if password -%} ?key={{ password }} {%- endif %}")
|
||||
(text "Edit"))
|
||||
|
||||
(div
|
||||
|
|
22
src/model.rs
22
src/model.rs
|
@ -95,6 +95,15 @@ pub struct EntryMetadata {
|
|||
/// If this entry shows up in search engines.
|
||||
#[serde(default, alias = "OPTION_DISABLE_SEARCH_ENGINE")]
|
||||
pub option_disable_search_engine: bool,
|
||||
/// The password that is required to view this entry.
|
||||
#[serde(default, alias = "OPTION_VIEW_PASSWORD")]
|
||||
pub option_view_password: String,
|
||||
/// The password that is required to view the source of the entry.
|
||||
///
|
||||
/// If no password is provided but a view password IS provided, the view
|
||||
/// password will be used.
|
||||
#[serde(default, alias = "OPTION_SOURCE_PASSWORD")]
|
||||
pub option_source_password: String,
|
||||
/// The theme that is automatically used when this entry is viewed.
|
||||
#[serde(default, alias = "ACCESS_RECOMMENDED_THEME")]
|
||||
pub access_recommended_theme: RecommendedTheme,
|
||||
|
@ -388,6 +397,17 @@ macro_rules! metadata_css {
|
|||
}
|
||||
};
|
||||
|
||||
($selector:expr, $property:literal !important, $field:ident->$output:ident) => {
|
||||
if !$field.is_empty() {
|
||||
$output.push_str(&format!(
|
||||
"{} {{ {}: {} !important; }}\n",
|
||||
$selector,
|
||||
$property,
|
||||
EntryMetadata::css_escape(&$field)
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
($selector:expr, $property:literal, $format:literal, $self:ident.$field:ident->$output:ident) => {
|
||||
if !$self.$field.is_empty() {
|
||||
$output.push_str(&format!(
|
||||
|
@ -404,7 +424,7 @@ macro_rules! text_size {
|
|||
($selector:literal, $split:ident, $idx:literal, $output:ident) => {
|
||||
if let Some(x) = $split.get($idx) {
|
||||
if *x != "default" && *x != "0" {
|
||||
metadata_css!($selector, "font-size", x->$output);
|
||||
metadata_css!($selector, "font-size" !important, x->$output);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ use crate::{
|
|||
};
|
||||
use axum::{
|
||||
Extension, Json, Router,
|
||||
extract::Path,
|
||||
extract::{Path, Query},
|
||||
http::{HeaderMap, HeaderValue},
|
||||
response::{Html, IntoResponse},
|
||||
routing::{get, get_service, post},
|
||||
|
@ -119,9 +119,16 @@ async fn view_doc_request(
|
|||
return Html(tera.render("doc.lisp", &ctx).unwrap());
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct ViewQuery {
|
||||
#[serde(default)]
|
||||
pub key: String,
|
||||
}
|
||||
|
||||
async fn view_request(
|
||||
Extension(data): Extension<State>,
|
||||
Path(slug): Path<String>,
|
||||
Query(props): Query<ViewQuery>,
|
||||
) -> impl IntoResponse {
|
||||
let (ref data, ref tera, ref build_code) = *data.read().await;
|
||||
let entry = match data
|
||||
|
@ -163,6 +170,15 @@ async fn view_request(
|
|||
return Html(tera.render("error.lisp", &ctx).unwrap());
|
||||
}
|
||||
|
||||
// ...
|
||||
if !metadata.option_view_password.is_empty()
|
||||
&& metadata.option_view_password != props.key.clone()
|
||||
{
|
||||
let mut ctx = default_context(&data, &build_code);
|
||||
ctx.insert("entry", &entry);
|
||||
return Html(tera.render("password.lisp", &ctx).unwrap());
|
||||
}
|
||||
|
||||
// pull views
|
||||
let views = if !metadata.option_disable_views {
|
||||
match data
|
||||
|
@ -207,6 +223,7 @@ async fn view_request(
|
|||
ctx.insert("metadata", &metadata);
|
||||
ctx.insert("metadata_head", &metadata.head_tags());
|
||||
ctx.insert("metadata_css", &metadata.css());
|
||||
ctx.insert("password", &props.key);
|
||||
|
||||
Html(tera.render("view.lisp", &ctx).unwrap())
|
||||
}
|
||||
|
@ -214,6 +231,7 @@ async fn view_request(
|
|||
async fn editor_request(
|
||||
Extension(data): Extension<State>,
|
||||
Path(slug): Path<String>,
|
||||
Query(props): Query<ViewQuery>,
|
||||
) -> impl IntoResponse {
|
||||
let (ref data, ref tera, ref build_code) = *data.read().await;
|
||||
let entry = match data
|
||||
|
@ -238,9 +256,34 @@ async fn editor_request(
|
|||
}
|
||||
};
|
||||
|
||||
let metadata: EntryMetadata = match toml::from_str(&EntryMetadata::ini_to_toml(&entry.metadata))
|
||||
{
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
let mut ctx = default_context(&data, &build_code);
|
||||
ctx.insert("error", &e.to_string());
|
||||
return Html(tera.render("error.lisp", &ctx).unwrap());
|
||||
}
|
||||
};
|
||||
|
||||
// ...
|
||||
if if !metadata.option_source_password.is_empty() {
|
||||
metadata.option_source_password != props.key
|
||||
} else if !metadata.option_view_password.is_empty() {
|
||||
metadata.option_view_password != props.key
|
||||
} else {
|
||||
false
|
||||
} {
|
||||
let mut ctx = default_context(&data, &build_code);
|
||||
ctx.insert("entry", &entry);
|
||||
return Html(tera.render("password.lisp", &ctx).unwrap());
|
||||
}
|
||||
|
||||
// ...
|
||||
let mut ctx = default_context(&data, &build_code);
|
||||
|
||||
ctx.insert("entry", &entry);
|
||||
ctx.insert("password", &props.key);
|
||||
|
||||
Html(tera.render("edit.lisp", &ctx).unwrap())
|
||||
}
|
||||
|
@ -311,6 +354,35 @@ fn default_random() -> String {
|
|||
salt()
|
||||
}
|
||||
|
||||
fn hash_passwords(metadata: &mut EntryMetadata) -> (bool, String) {
|
||||
// hash passwords
|
||||
let do_update_metadata = (!metadata.option_view_password.is_empty()
|
||||
|| !metadata.option_source_password.is_empty())
|
||||
&& (!metadata.option_view_password.starts_with("h:")
|
||||
|| !metadata.option_source_password.starts_with("h:"));
|
||||
|
||||
if !metadata.option_view_password.is_empty() && !metadata.option_view_password.starts_with("h:")
|
||||
{
|
||||
metadata.option_view_password =
|
||||
format!("h:{}", hash(metadata.option_view_password.clone()));
|
||||
}
|
||||
|
||||
if !metadata.option_source_password.is_empty()
|
||||
&& !metadata.option_source_password.starts_with("h:")
|
||||
{
|
||||
metadata.option_source_password =
|
||||
format!("h:{}", hash(metadata.option_source_password.clone()));
|
||||
}
|
||||
|
||||
if do_update_metadata {
|
||||
if let Ok(x) = toml::to_string_pretty(&metadata) {
|
||||
return (true, x);
|
||||
};
|
||||
}
|
||||
|
||||
(false, String::new())
|
||||
}
|
||||
|
||||
/// The time that must be waited between each entry creation.
|
||||
const CREATE_WAIT_TIME: usize = 5000;
|
||||
|
||||
|
@ -318,7 +390,7 @@ async fn create_request(
|
|||
jar: CookieJar,
|
||||
headers: HeaderMap,
|
||||
Extension(data): Extension<State>,
|
||||
Json(req): Json<CreateEntry>,
|
||||
Json(mut req): Json<CreateEntry>,
|
||||
) -> std::result::Result<impl IntoResponse, Json<ApiReturn<()>>> {
|
||||
let (ref data, _, _) = *data.read().await;
|
||||
|
||||
|
@ -382,7 +454,8 @@ async fn create_request(
|
|||
}
|
||||
|
||||
// check metadata
|
||||
let metadata: EntryMetadata = match toml::from_str(&EntryMetadata::ini_to_toml(&req.metadata)) {
|
||||
let mut metadata: EntryMetadata =
|
||||
match toml::from_str(&EntryMetadata::ini_to_toml(&req.metadata)) {
|
||||
Ok(x) => x,
|
||||
Err(e) => return Err(Json(Error::MiscError(e.to_string()).into())),
|
||||
};
|
||||
|
@ -391,6 +464,11 @@ async fn create_request(
|
|||
return Err(Json(Error::MiscError(e.to_string()).into()));
|
||||
}
|
||||
|
||||
let (do_update_metadata, updated) = hash_passwords(&mut metadata);
|
||||
if do_update_metadata {
|
||||
req.metadata = updated;
|
||||
}
|
||||
|
||||
// check for existing
|
||||
if data
|
||||
.query(&SimplifiedQuery {
|
||||
|
@ -471,7 +549,7 @@ async fn edit_request(
|
|||
headers: HeaderMap,
|
||||
Extension(data): Extension<State>,
|
||||
Path(slug): Path<String>,
|
||||
Json(req): Json<EditEntry>,
|
||||
Json(mut req): Json<EditEntry>,
|
||||
) -> impl IntoResponse {
|
||||
let (ref data, _, _) = *data.read().await;
|
||||
|
||||
|
@ -500,7 +578,8 @@ async fn edit_request(
|
|||
}
|
||||
|
||||
// check metadata
|
||||
let metadata: EntryMetadata = match toml::from_str(&EntryMetadata::ini_to_toml(&req.metadata)) {
|
||||
let mut metadata: EntryMetadata =
|
||||
match toml::from_str(&EntryMetadata::ini_to_toml(&req.metadata)) {
|
||||
Ok(x) => x,
|
||||
Err(e) => return Json(Error::MiscError(e.to_string()).into()),
|
||||
};
|
||||
|
@ -509,6 +588,11 @@ async fn edit_request(
|
|||
return Json(Error::MiscError(e.to_string()).into());
|
||||
}
|
||||
|
||||
let (do_update_metadata, updated) = hash_passwords(&mut metadata);
|
||||
if do_update_metadata {
|
||||
req.metadata = updated;
|
||||
}
|
||||
|
||||
// ...
|
||||
let (id, mut entry) = match data
|
||||
.query(&SimplifiedQuery {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue