add: check for ip bans
This commit is contained in:
parent
06b0aa0b4c
commit
70adae5b66
9 changed files with 84 additions and 47 deletions
48
Cargo.lock
generated
48
Cargo.lock
generated
|
@ -82,30 +82,6 @@ version = "1.1.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
|
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "attobin"
|
|
||||||
version = "0.2.1"
|
|
||||||
dependencies = [
|
|
||||||
"axum",
|
|
||||||
"axum-extra",
|
|
||||||
"dotenv",
|
|
||||||
"glob",
|
|
||||||
"nanoneo",
|
|
||||||
"pathbufd",
|
|
||||||
"regex",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"serde_valid",
|
|
||||||
"tera",
|
|
||||||
"tetratto-core",
|
|
||||||
"tetratto-shared",
|
|
||||||
"tokio",
|
|
||||||
"toml 0.9.2",
|
|
||||||
"tower-http",
|
|
||||||
"tracing",
|
|
||||||
"tracing-subscriber",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "autocfg"
|
name = "autocfg"
|
||||||
version = "1.5.0"
|
version = "1.5.0"
|
||||||
|
@ -629,6 +605,30 @@ dependencies = [
|
||||||
"miniz_oxide",
|
"miniz_oxide",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fluffle"
|
||||||
|
version = "0.2.1"
|
||||||
|
dependencies = [
|
||||||
|
"axum",
|
||||||
|
"axum-extra",
|
||||||
|
"dotenv",
|
||||||
|
"glob",
|
||||||
|
"nanoneo",
|
||||||
|
"pathbufd",
|
||||||
|
"regex",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"serde_valid",
|
||||||
|
"tera",
|
||||||
|
"tetratto-core",
|
||||||
|
"tetratto-shared",
|
||||||
|
"tokio",
|
||||||
|
"toml 0.9.2",
|
||||||
|
"tower-http",
|
||||||
|
"tracing",
|
||||||
|
"tracing-subscriber",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fnv"
|
name = "fnv"
|
||||||
version = "1.0.7"
|
version = "1.0.7"
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
[package]
|
[package]
|
||||||
name = "attobin"
|
name = "fluffle"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
authors = ["trisuaso"]
|
authors = ["trisuaso"]
|
||||||
repository = "https://trisua.com/t/tetratto"
|
repository = "https://trisua.com/t/fluffle"
|
||||||
license = "AGPL-3.0-or-later"
|
license = "AGPL-3.0-or-later"
|
||||||
homepage = "https://tetratto.com"
|
homepage = "https://fluffle.cc"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tetratto-core = "12.0.2"
|
tetratto-core = "12.0.2"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# 🦌 attobin
|
# 🐰 fluffle
|
||||||
|
|
||||||
Attobin is a simple Rentry (~December 2024) clone that uses a Tetratto app as a backend.
|
Fluffle is a familiar Markdown pastebin-y site :)
|
||||||
|
|
||||||
Since Tetratto is used as a backend, you'll obviously need to create an app at <https://tetratto.com/developer>. Once you've created the app, scroll down to "Secret key" and roll the key. Copy the key since you'll need it for later.
|
Since Tetratto is used as a backend, you'll obviously need to create an app at <https://tetratto.com/developer>. Once you've created the app, scroll down to "Secret key" and roll the key. Copy the key since you'll need it for later.
|
||||||
|
|
||||||
|
@ -34,10 +34,10 @@ You can build (without running) for release using the following command:
|
||||||
cargo build -r
|
cargo build -r
|
||||||
```
|
```
|
||||||
|
|
||||||
Once you've built the binary, it'll be located at (from the root `attobin/` directory) `target/release/attobin`.
|
Once you've built the binary, it'll be located at (from the root `fluffle/` directory) `target/release/fluffle`.
|
||||||
|
|
||||||
All templates are compiled with [nanoneo](https://trisua.com/t/nanoneo), so it's recommended that you familiarize yourself with that syntax.
|
All templates are compiled with [nanoneo](https://trisua.com/t/nanoneo), so it's recommended that you familiarize yourself with that syntax.
|
||||||
|
|
||||||
## Attribution
|
## Attribution
|
||||||
|
|
||||||
Attobin is licensed under the AGPL-3.0 license. Tetratto is also licensed under the AGPL-3.0 license. Attobin is not affiliated with [Tetratto](https://tetratto.com) or [Rentry](https://rentry.co).
|
Fluffle is licensed under the AGPL-3.0 license. Tetratto is also licensed under the AGPL-3.0 license.
|
||||||
|
|
|
@ -8,4 +8,4 @@ All option names can either be in all lowercase, or all uppercase. While option
|
||||||
|
|
||||||
Metadata options go in the "Metadata" tab in the entry editor page. Each option should be on a new line, and should be formatted as `NAME = value`. If you're familiar with TOML, you should be comfortable with metadata formatting.
|
Metadata options go in the "Metadata" tab in the entry editor page. Each option should be on a new line, and should be formatted as `NAME = value`. If you're familiar with TOML, you should be comfortable with metadata formatting.
|
||||||
|
|
||||||
You can view a list of all options and what they do [here](/public/reference/attobin/model/struct.EntryMetadata.html#fields).
|
You can view a list of all options and what they do [here](/public/reference/fluffle/model/struct.EntryMetadata.html#fields).
|
||||||
|
|
|
@ -4,8 +4,8 @@ function media_theme_pref() {
|
||||||
|
|
||||||
if (
|
if (
|
||||||
window.matchMedia("(prefers-color-scheme: dark)").matches &&
|
window.matchMedia("(prefers-color-scheme: dark)").matches &&
|
||||||
(!window.localStorage.getItem("attobin:theme") ||
|
(!window.localStorage.getItem("fluffle:theme") ||
|
||||||
window.localStorage.getItem("attobin:theme") === "Auto")
|
window.localStorage.getItem("fluffle:theme") === "Auto")
|
||||||
) {
|
) {
|
||||||
document.documentElement.classList.add("dark");
|
document.documentElement.classList.add("dark");
|
||||||
|
|
||||||
|
@ -13,16 +13,16 @@ function media_theme_pref() {
|
||||||
document.getElementById("switch_dark").classList.remove("hidden");
|
document.getElementById("switch_dark").classList.remove("hidden");
|
||||||
} else if (
|
} else if (
|
||||||
window.matchMedia("(prefers-color-scheme: light)").matches &&
|
window.matchMedia("(prefers-color-scheme: light)").matches &&
|
||||||
(!window.localStorage.getItem("attobin:theme") ||
|
(!window.localStorage.getItem("fluffle:theme") ||
|
||||||
window.localStorage.getItem("attobin:theme") === "Auto")
|
window.localStorage.getItem("fluffle:theme") === "Auto")
|
||||||
) {
|
) {
|
||||||
document.documentElement.classList.remove("dark");
|
document.documentElement.classList.remove("dark");
|
||||||
|
|
||||||
document.getElementById("switch_light").classList.remove("hidden");
|
document.getElementById("switch_light").classList.remove("hidden");
|
||||||
document.getElementById("switch_dark").classList.add("hidden");
|
document.getElementById("switch_dark").classList.add("hidden");
|
||||||
} else if (window.localStorage.getItem("attobin:theme")) {
|
} else if (window.localStorage.getItem("fluffle:theme")) {
|
||||||
/* restore theme */
|
/* restore theme */
|
||||||
const current = window.localStorage.getItem("attobin:theme");
|
const current = window.localStorage.getItem("fluffle:theme");
|
||||||
document.documentElement.className = current.toLowerCase();
|
document.documentElement.className = current.toLowerCase();
|
||||||
|
|
||||||
if (current === "Light") {
|
if (current === "Light") {
|
||||||
|
@ -48,7 +48,7 @@ globalThis.temporary_set_theme = (theme) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
globalThis.set_theme = (theme) => {
|
globalThis.set_theme = (theme) => {
|
||||||
window.localStorage.setItem("attobin:theme", theme);
|
window.localStorage.setItem("fluffle:theme", theme);
|
||||||
document.documentElement.className = theme;
|
document.documentElement.className = theme;
|
||||||
media_theme_pref();
|
media_theme_pref();
|
||||||
};
|
};
|
||||||
|
|
|
@ -44,7 +44,7 @@
|
||||||
(text "what"))
|
(text "what"))
|
||||||
(a
|
(a
|
||||||
("class" "button")
|
("class" "button")
|
||||||
("href" "https://trisua.com/t/attobin")
|
("href" "https://trisua.com/t/fluffle")
|
||||||
(text "source"))))
|
(text "source"))))
|
||||||
|
|
||||||
(a
|
(a
|
||||||
|
|
|
@ -121,7 +121,7 @@ async fn main() {
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
info!("🦌 attobin.");
|
info!("🐰 fluffle.");
|
||||||
info!("listening on http://0.0.0.0:{}", port);
|
info!("listening on http://0.0.0.0:{}", port);
|
||||||
axum::serve(
|
axum::serve(
|
||||||
listener,
|
listener,
|
||||||
|
|
|
@ -13,6 +13,9 @@ pub struct Entry {
|
||||||
pub content: String,
|
pub content: String,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub metadata: String,
|
pub metadata: String,
|
||||||
|
/// The IP address of the last editor of the entry.
|
||||||
|
#[serde(default)]
|
||||||
|
pub last_edit_from: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, PartialEq, Eq)]
|
#[derive(Serialize, Deserialize, PartialEq, Eq)]
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
use std::env::var;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
State,
|
State,
|
||||||
model::{Entry, EntryMetadata},
|
model::{Entry, EntryMetadata},
|
||||||
|
@ -5,6 +7,7 @@ use crate::{
|
||||||
use axum::{
|
use axum::{
|
||||||
Extension, Json, Router,
|
Extension, Json, Router,
|
||||||
extract::Path,
|
extract::Path,
|
||||||
|
http::{HeaderMap, HeaderValue},
|
||||||
response::{Html, IntoResponse},
|
response::{Html, IntoResponse},
|
||||||
routing::{get, get_service, post},
|
routing::{get, get_service, post},
|
||||||
};
|
};
|
||||||
|
@ -48,18 +51,15 @@ pub fn routes() -> Router {
|
||||||
|
|
||||||
fn default_context(data: &DataClient, build_code: &str) -> Context {
|
fn default_context(data: &DataClient, build_code: &str) -> Context {
|
||||||
let mut ctx = Context::new();
|
let mut ctx = Context::new();
|
||||||
ctx.insert(
|
ctx.insert("name", &var("NAME").unwrap_or("Fluffle".to_string()));
|
||||||
"name",
|
|
||||||
&std::env::var("NAME").unwrap_or("Attobin".to_string()),
|
|
||||||
);
|
|
||||||
ctx.insert(
|
ctx.insert(
|
||||||
"theme_color",
|
"theme_color",
|
||||||
&std::env::var("THEME_COLOR").unwrap_or("#fbc27f".to_string()),
|
&var("THEME_COLOR").unwrap_or("#a3b3ff".to_string()),
|
||||||
);
|
);
|
||||||
ctx.insert("tetratto", &data.host);
|
ctx.insert("tetratto", &data.host);
|
||||||
ctx.insert(
|
ctx.insert(
|
||||||
"what_page_slug",
|
"what_page_slug",
|
||||||
&std::env::var("WHAT_SLUG").unwrap_or("what".to_string()),
|
&var("WHAT_SLUG").unwrap_or("what".to_string()),
|
||||||
);
|
);
|
||||||
ctx.insert("build_code", &build_code);
|
ctx.insert("build_code", &build_code);
|
||||||
ctx
|
ctx
|
||||||
|
@ -306,11 +306,27 @@ const CREATE_WAIT_TIME: usize = 5000;
|
||||||
|
|
||||||
async fn create_request(
|
async fn create_request(
|
||||||
jar: CookieJar,
|
jar: CookieJar,
|
||||||
|
headers: HeaderMap,
|
||||||
Extension(data): Extension<State>,
|
Extension(data): Extension<State>,
|
||||||
Json(req): Json<CreateEntry>,
|
Json(req): Json<CreateEntry>,
|
||||||
) -> std::result::Result<impl IntoResponse, Json<ApiReturn<()>>> {
|
) -> std::result::Result<impl IntoResponse, Json<ApiReturn<()>>> {
|
||||||
let (ref data, _, _) = *data.read().await;
|
let (ref data, _, _) = *data.read().await;
|
||||||
|
|
||||||
|
// get real ip
|
||||||
|
let real_ip = headers
|
||||||
|
.get(var("REAL_IP_HEADER").unwrap_or("CF-Connecting-IP".to_string()))
|
||||||
|
.unwrap_or(&HeaderValue::from_static(""))
|
||||||
|
.to_str()
|
||||||
|
.unwrap_or("")
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
// check for ip ban
|
||||||
|
if !real_ip.is_empty() {
|
||||||
|
if data.check_ip(&real_ip.as_str()).await.is_ok() {
|
||||||
|
return Err(Json(Error::NotAllowed.into()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// check wait time
|
// check wait time
|
||||||
if let Some(cookie) = jar.get("__Secure-Claim-Next") {
|
if let Some(cookie) = jar.get("__Secure-Claim-Next") {
|
||||||
if unix_epoch_timestamp()
|
if unix_epoch_timestamp()
|
||||||
|
@ -394,6 +410,7 @@ async fn create_request(
|
||||||
edited: created,
|
edited: created,
|
||||||
content: req.content,
|
content: req.content,
|
||||||
metadata: req.metadata,
|
metadata: req.metadata,
|
||||||
|
last_edit_from: real_ip,
|
||||||
})
|
})
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
)
|
)
|
||||||
|
@ -441,12 +458,28 @@ struct EditEntry {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn edit_request(
|
async fn edit_request(
|
||||||
|
headers: HeaderMap,
|
||||||
Extension(data): Extension<State>,
|
Extension(data): Extension<State>,
|
||||||
Path(slug): Path<String>,
|
Path(slug): Path<String>,
|
||||||
Json(req): Json<EditEntry>,
|
Json(req): Json<EditEntry>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
let (ref data, _, _) = *data.read().await;
|
let (ref data, _, _) = *data.read().await;
|
||||||
|
|
||||||
|
// get real ip
|
||||||
|
let real_ip = headers
|
||||||
|
.get(var("REAL_IP_HEADER").unwrap_or("CF-Connecting-IP".to_string()))
|
||||||
|
.unwrap_or(&HeaderValue::from_static(""))
|
||||||
|
.to_str()
|
||||||
|
.unwrap_or("")
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
// check for ip ban
|
||||||
|
if !real_ip.is_empty() {
|
||||||
|
if data.check_ip(&real_ip.as_str()).await.is_ok() {
|
||||||
|
return Json(Error::NotAllowed.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// check content length
|
// check content length
|
||||||
if req.content.len() < 2 {
|
if req.content.len() < 2 {
|
||||||
return Json(Error::DataTooShort("content".to_string()).into());
|
return Json(Error::DataTooShort("content".to_string()).into());
|
||||||
|
@ -586,6 +619,7 @@ async fn edit_request(
|
||||||
// update
|
// update
|
||||||
entry.content = req.content;
|
entry.content = req.content;
|
||||||
entry.metadata = req.metadata;
|
entry.metadata = req.metadata;
|
||||||
|
entry.last_edit_from = real_ip;
|
||||||
entry.edited = unix_epoch_timestamp();
|
entry.edited = unix_epoch_timestamp();
|
||||||
|
|
||||||
if let Err(e) = data
|
if let Err(e) = data
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue