mirror of
https://github.com/cupcakearmy/cryptgeon.git
synced 2026-07-05 06:55:30 +00:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0c33f2f34c | |||
| 6b29c6f069 |
@@ -80,6 +80,7 @@ of the notes even if it tried to.
|
||||
| `ALLOW_ADVANCED` | `true` | Allow custom configuration. If set to `false` all notes will be one view only. |
|
||||
| `ALLOW_FILES` | `true` | Allow uploading files. If set to `false`, users will only be allowed to create text notes. |
|
||||
| `ID_LENGTH` | `32` | Set the size of the note `id` in bytes. By default this is `32` bytes. This is useful for reducing link size. _This setting does not affect encryption strength_. |
|
||||
| `REDIS_PREFIX` | `""` | Optional prefix for all Redis keys. Useful when sharing a Redis instance with other apps via ACL namespaces. |
|
||||
| `VERBOSITY` | `warn` | Verbosity level for the backend. [Possible values](https://docs.rs/env_logger/latest/env_logger/#enabling-logging) are: `error`, `warn`, `info`, `debug`, `trace` |
|
||||
| `THEME_IMAGE` | `""` | Custom image for replacing the logo. Must be publicly reachable |
|
||||
| `THEME_TEXT` | `""` | Custom text for replacing the description below the logo |
|
||||
@@ -89,6 +90,7 @@ of the notes even if it tried to.
|
||||
| `THEME_HOME_LINK` | `true` | Show the `/home` link in the footer. Defaults to `true`. |
|
||||
| `IMPRINT_URL` | `""` | Custom url for an Imprint hosted somewhere else. Must be publicly reachable. Takes precedence above `IMPRINT_HTML`. |
|
||||
| `IMPRINT_HTML` | `""` | Alternative to `IMPRINT_URL`, this can be used to specify the HTML code to show on `/imprint`. Only `IMPRINT_HTML` or `IMPRINT_URL` should be specified, not both. |
|
||||
|
||||
## Deployment
|
||||
|
||||
> ℹ️ `https` is required otherwise browsers will not support the cryptographic functions.
|
||||
@@ -102,7 +104,7 @@ Docker is the easiest way. There is the [official image here](https://hub.docker
|
||||
```yaml
|
||||
# docker-compose.yml
|
||||
|
||||
version: '3.8'
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
redis:
|
||||
|
||||
@@ -34,6 +34,10 @@ pub static ref ID_LENGTH: u32 = std::env::var("ID_LENGTH")
|
||||
.unwrap_or("32".to_string())
|
||||
.parse()
|
||||
.unwrap();
|
||||
pub static ref REDIS_PREFIX: String = std::env::var("REDIS_PREFIX")
|
||||
.unwrap_or("".to_string())
|
||||
.parse()
|
||||
.unwrap();
|
||||
pub static ref ALLOW_FILES: bool = std::env::var("ALLOW_FILES")
|
||||
.unwrap_or("true".to_string())
|
||||
.parse()
|
||||
|
||||
@@ -1,16 +1,35 @@
|
||||
use axum::{body::Body, extract::Request, http::HeaderValue, middleware::Next, response::Response};
|
||||
use axum::{
|
||||
http::HeaderValue,
|
||||
response::{Html, IntoResponse, Response},
|
||||
};
|
||||
use ring::rand::SecureRandom;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
const CUSTOM_HEADER_NAME: &str = "Content-Security-Policy";
|
||||
const CUSTOM_HEADER_VALUE: &str = "default-src 'self'; script-src 'report-sample' 'self'; style-src 'report-sample' 'self'; object-src 'none'; base-uri 'self'; connect-src 'self' data:; font-src 'self'; frame-src 'self'; img-src 'self'; manifest-src 'self'; media-src 'self'; worker-src 'none';";
|
||||
const CSP_POLICY: &str = "default-src 'self'; script-src 'nonce-{nonce}' 'strict-dynamic'; style-src 'self' 'unsafe-inline'; object-src 'none'; base-uri 'self'; connect-src 'self'";
|
||||
|
||||
lazy_static! {
|
||||
static ref HEADER_VALUE: HeaderValue = HeaderValue::from_static(CUSTOM_HEADER_VALUE);
|
||||
fn index_html() -> &'static str {
|
||||
static HTML: OnceLock<String> = OnceLock::new();
|
||||
HTML.get_or_init(|| {
|
||||
let path = format!("{}index.html", *crate::config::FRONTEND_PATH);
|
||||
std::fs::read_to_string(&path).expect("Failed to read index.html for CSP injection")
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn add_csp_header(request: Request<Body>, next: Next) -> Response {
|
||||
let mut response = next.run(request).await;
|
||||
fn generate_nonce() -> String {
|
||||
let rng = ring::rand::SystemRandom::new();
|
||||
let mut bytes = [0u8; 32];
|
||||
rng.fill(&mut bytes).expect("Failed to generate CSP nonce");
|
||||
bs62::encode_data(&bytes)
|
||||
}
|
||||
|
||||
pub async fn spa_fallback() -> Response {
|
||||
let nonce = generate_nonce();
|
||||
let csp = CSP_POLICY.replace("{nonce}", &nonce);
|
||||
let html = index_html().replace("<script>", &format!("<script nonce=\"{}\">", nonce));
|
||||
|
||||
let mut response = Html(html).into_response();
|
||||
response
|
||||
.headers_mut()
|
||||
.append(CUSTOM_HEADER_NAME, HEADER_VALUE.clone());
|
||||
.insert("Content-Security-Policy", HeaderValue::from_str(&csp).unwrap());
|
||||
response
|
||||
}
|
||||
@@ -12,7 +12,7 @@ use tower::Layer;
|
||||
use tower_http::{
|
||||
compression::CompressionLayer,
|
||||
normalize_path::NormalizePathLayer,
|
||||
services::{ServeDir, ServeFile},
|
||||
services::ServeDir,
|
||||
};
|
||||
|
||||
#[macro_use]
|
||||
@@ -50,14 +50,12 @@ async fn main() {
|
||||
.merge(health_routes)
|
||||
.merge(status_routes);
|
||||
|
||||
let index = format!("{}{}", config::FRONTEND_PATH.to_string(), "/index.html");
|
||||
let serve_dir =
|
||||
ServeDir::new(config::FRONTEND_PATH.to_string()).not_found_service(ServeFile::new(index));
|
||||
let app = Router::new()
|
||||
.nest("/api", api_routes)
|
||||
.fallback_service(serve_dir)
|
||||
// Disabled for now, as svelte inlines scripts
|
||||
// .layer(middleware::from_fn(csp::add_csp_header))
|
||||
.fallback_service(
|
||||
ServeDir::new(config::FRONTEND_PATH.to_string())
|
||||
.not_found_service(axum::Router::new().fallback(csp::spa_fallback)),
|
||||
)
|
||||
.layer(DefaultBodyLimit::max(*config::LIMIT))
|
||||
.layer(
|
||||
CompressionLayer::new()
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use redis;
|
||||
use redis::Commands;
|
||||
|
||||
use crate::config;
|
||||
use crate::note::now;
|
||||
use crate::note::Note;
|
||||
|
||||
@@ -11,6 +12,10 @@ lazy_static! {
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn prefixed(id: &String) -> String {
|
||||
format!("{}{}", config::REDIS_PREFIX.as_str(), id)
|
||||
}
|
||||
|
||||
fn get_connection() -> Result<redis::Connection, &'static str> {
|
||||
let client =
|
||||
redis::Client::open(REDIS_CLIENT.to_string()).map_err(|_| "Unable to connect to redis")?;
|
||||
@@ -28,15 +33,16 @@ pub fn can_reach_redis() -> bool {
|
||||
}
|
||||
|
||||
pub fn set(id: &String, note: &Note) -> Result<(), &'static str> {
|
||||
let key = prefixed(id);
|
||||
let serialized = serde_json::to_string(¬e.clone()).unwrap();
|
||||
let mut conn = get_connection()?;
|
||||
|
||||
conn.set::<_, _, ()>(id, serialized)
|
||||
conn.set::<_, _, ()>(key.as_str(), serialized)
|
||||
.map_err(|_| "Unable to set note in redis")?;
|
||||
match note.expiration {
|
||||
Some(e) => {
|
||||
let seconds = e - now();
|
||||
conn.expire::<_, ()>(id, seconds as i64)
|
||||
conn.expire::<_, ()>(key.as_str(), seconds as i64)
|
||||
.map_err(|_| "Unable to set expiration on note")?
|
||||
}
|
||||
None => {}
|
||||
@@ -45,8 +51,9 @@ pub fn set(id: &String, note: &Note) -> Result<(), &'static str> {
|
||||
}
|
||||
|
||||
pub fn get(id: &String) -> Result<Option<Note>, &'static str> {
|
||||
let key = prefixed(id);
|
||||
let mut conn = get_connection()?;
|
||||
let value: Option<String> = conn.get(id).map_err(|_| "Could not load note in redis")?;
|
||||
let value: Option<String> = conn.get(key.as_str()).map_err(|_| "Could not load note in redis")?;
|
||||
match value {
|
||||
None => return Ok(None),
|
||||
Some(s) => {
|
||||
@@ -57,7 +64,8 @@ pub fn get(id: &String) -> Result<Option<Note>, &'static str> {
|
||||
}
|
||||
|
||||
pub fn del(id: &String) -> Result<(), &'static str> {
|
||||
let key = prefixed(id);
|
||||
let mut conn = get_connection()?;
|
||||
conn.del::<_, ()>(id).map_err(|_| "Unable to delete note in redis")?;
|
||||
conn.del::<_, ()>(key.as_str()).map_err(|_| "Unable to delete note in redis")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user