Use encrypt image names in html
This commit is contained in:
54
src/main.rs
54
src/main.rs
@ -1,13 +1,13 @@
|
||||
use std::{net::SocketAddr, path::{Path, PathBuf}, ffi::OsStr, fs::File, io::{BufReader, Seek, Cursor, BufRead}, time::SystemTime, env::args_os};
|
||||
use std::{net::SocketAddr, path::{Path, PathBuf}, ffi::{OsStr, OsString}, fs::File, io::{BufReader, Seek, Cursor, BufRead}, time::SystemTime, env::args_os, os::unix::prelude::OsStrExt};
|
||||
|
||||
use anyhow::{Context, Result, anyhow};
|
||||
use axum::{Router, routing::{get, post}, response::{IntoResponse, Redirect}, http::{StatusCode, header}, extract::{self, State}, Form, handler::Handler};
|
||||
use axum_sessions::{async_session::CookieStore, SessionLayer, extractors::{ReadableSession, WritableSession}};
|
||||
use axum_template::{RenderHtml, engine::Engine};
|
||||
use chacha20poly1305::{XChaCha20Poly1305, KeyInit, AeadCore, aead::{OsRng, rand_core::RngCore, Aead}, XNonce};
|
||||
use error::AppError;
|
||||
use exif::{Tag, Value, Field, In, Exif};
|
||||
use image::{imageops::FilterType, DynamicImage};
|
||||
use rand::Rng;
|
||||
use serde::{Serialize, Deserialize};
|
||||
use tokio::task;
|
||||
use tower_http::{trace::{self, TraceLayer}, compression::CompressionLayer};
|
||||
@ -25,17 +25,22 @@ mod error;
|
||||
|
||||
type TemplateEngine = Engine<minijinja::Environment<'static>>;
|
||||
type ImageDir = PathBuf;
|
||||
type SecretKey = [u8; 64];
|
||||
|
||||
#[derive(Clone, extract::FromRef)]
|
||||
struct ApplicationState {
|
||||
engine: TemplateEngine,
|
||||
image_dir: ImageDir,
|
||||
secret_key: SecretKey,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let image_path = args_os().nth(1).expect("Usage: image-gallery IMAGE_DIRECTORY");
|
||||
|
||||
let mut secret_key: SecretKey = [0; 64];
|
||||
OsRng.fill_bytes(&mut secret_key);
|
||||
|
||||
let default_tracing = "image_gallery=debug,tower_http=info".into();
|
||||
let tracing_filter = EnvFilter::try_from_default_env().unwrap_or(default_tracing);
|
||||
let tracing_formatter = tracing_subscriber::fmt::layer()
|
||||
@ -51,15 +56,14 @@ async fn main() {
|
||||
jinja.add_template("index", include_str!("../templates/index.html")).unwrap();
|
||||
jinja.add_template("login", include_str!("../templates/login.html")).unwrap();
|
||||
|
||||
let secret = rand::thread_rng().gen::<[u8; 128]>();
|
||||
let session_layer = SessionLayer::new(CookieStore::new(), &secret)
|
||||
let session_layer = SessionLayer::new(CookieStore::new(), &secret_key)
|
||||
.with_cookie_name("session")
|
||||
.with_session_ttl(None);
|
||||
|
||||
let app = Router::new()
|
||||
.route("/", get(index.layer(CompressionLayer::new())))
|
||||
.route("/authenticate", post(authenticate))
|
||||
.route("/image/:image", get(converted_image))
|
||||
.route("/image/:encrypted_filename_hex", get(converted_image))
|
||||
.layer(TraceLayer::new_for_http()
|
||||
.make_span_with(trace::DefaultMakeSpan::new().level(Level::INFO))
|
||||
.on_response(trace::DefaultOnResponse::new().level(Level::INFO))
|
||||
@ -68,6 +72,7 @@ async fn main() {
|
||||
.with_state(ApplicationState {
|
||||
engine: Engine::from(jinja),
|
||||
image_dir: PathBuf::from(image_path),
|
||||
secret_key,
|
||||
});
|
||||
|
||||
let addr = SocketAddr::from(([0, 0, 0, 0], 3000));
|
||||
@ -78,11 +83,14 @@ async fn main() {
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[derive(Debug, Serialize, Default)]
|
||||
pub struct ImageInfo {
|
||||
width: u32,
|
||||
height: u32,
|
||||
name: String,
|
||||
#[serde(with = "hex")]
|
||||
encrypted_name: Vec<u8>,
|
||||
#[serde(skip_serializing)]
|
||||
name: OsString,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
@ -94,6 +102,7 @@ pub struct IndexTempalte {
|
||||
async fn index(
|
||||
engine: TemplateEngine,
|
||||
State(image_dir): State<ImageDir>,
|
||||
State(secret_key): State<SecretKey>,
|
||||
session: ReadableSession,
|
||||
) -> Result<impl IntoResponse, AppError> {
|
||||
let logged_in = session.get::<()>("logged_in").is_some();
|
||||
@ -101,6 +110,19 @@ async fn index(
|
||||
if logged_in {
|
||||
let images = read_images(&image_dir)?;
|
||||
|
||||
// Encrypt image names
|
||||
let chacha_key = chacha20poly1305::Key::from_slice(&secret_key[0..32]);
|
||||
let chacha = XChaCha20Poly1305::new(chacha_key);
|
||||
let images = images.into_iter().map(|mut image_info| {
|
||||
let nonce = XChaCha20Poly1305::generate_nonce(&mut OsRng);
|
||||
let mut ciphertext = chacha.encrypt(&nonce, image_info.name.as_bytes())?;
|
||||
|
||||
image_info.encrypted_name = nonce.as_slice().to_vec();
|
||||
image_info.encrypted_name.append(&mut ciphertext);
|
||||
|
||||
Ok(image_info)
|
||||
}).collect::<Result<Vec<_>>>()?;
|
||||
|
||||
let images = images.into_iter().rev().collect();
|
||||
|
||||
Ok(RenderHtml("index", engine, IndexTempalte {
|
||||
@ -131,15 +153,26 @@ async fn authenticate(
|
||||
}
|
||||
|
||||
async fn converted_image(
|
||||
extract::Path(image): extract::Path<String>,
|
||||
extract::Path(encrypted_filename_hex): extract::Path<String>,
|
||||
State(image_dir): State<ImageDir>,
|
||||
State(secret_key): State<SecretKey>,
|
||||
session: ReadableSession,
|
||||
) -> Result<impl IntoResponse, AppError> {
|
||||
session.get::<()>("logged_in")
|
||||
.ok_or(anyhow!("Trying to load image while not logged in!"))
|
||||
.context(StatusCode::FORBIDDEN)?;
|
||||
|
||||
let image_path = image_dir.join(image);
|
||||
// Decrypt image name
|
||||
let image_name = {
|
||||
let encrypted_filename = hex::decode(&encrypted_filename_hex)?;
|
||||
let nonce = XNonce::from_slice(&encrypted_filename[0..24]);
|
||||
let ciphertext = &encrypted_filename[24..];
|
||||
let chacha_key = chacha20poly1305::Key::from_slice(&secret_key[0..32]);
|
||||
let chacha = XChaCha20Poly1305::new(chacha_key);
|
||||
chacha.decrypt(nonce, ciphertext)
|
||||
}.with_context(|| format!("Could not decrypt filename {}", encrypted_filename_hex))?;
|
||||
|
||||
let image_path = image_dir.join(OsStr::from_bytes(&image_name));
|
||||
|
||||
image_path.exists()
|
||||
.then_some(())
|
||||
@ -279,7 +312,8 @@ fn read_image_info(path: &Path) -> Result<ImageInfo> {
|
||||
Ok(ImageInfo {
|
||||
width,
|
||||
height,
|
||||
name: path.file_name().expect("invalid file path").to_string_lossy().to_string(),
|
||||
name: path.file_name().expect("invalid file path").to_owned(),
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user