Skip to content

Commit

Permalink
feat: optional encryption (opt-out)
Browse files Browse the repository at this point in the history
  • Loading branch information
ChecksumDev committed Oct 22, 2023
1 parent a5e9aa3 commit 4697443
Show file tree
Hide file tree
Showing 6 changed files with 65 additions and 34 deletions.
1 change: 1 addition & 0 deletions migrations/20231022210311_optional_encryption.down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE files DROP COLUMN encrypted;
1 change: 1 addition & 0 deletions migrations/20231022210311_optional_encryption.up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE files ADD COLUMN encrypted INTEGER NOT NULL DEFAULT 1;
8 changes: 6 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use sqlx::{
};
use storage::Storage;

use crate::routes::{file::file_routes, user::user_routes, gen::gen_routes};
use crate::routes::{file::file_routes, gen::gen_routes, user::user_routes};

struct ConfigCache {
public_url: String,
Expand Down Expand Up @@ -46,7 +46,11 @@ async fn main() -> Result<()> {

// todo: support other databases (mysql, postgresql, etc)
sqlx::migrate!().run(&pool).await?;
let data = Data::new(AppData { pool, config, storage });
let data = Data::new(AppData {
pool,
config,
storage,
});

let bind = std::env::var("BIND").expect("BIND not set in environment");
println!("Lumen is running on {}", bind);
Expand Down
1 change: 1 addition & 0 deletions src/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ pub struct File {
pub r#type: String,
pub hash: String,
pub size: i64,
pub encrypted: bool,
pub user_id: i64,
pub created_at: NaiveDateTime,
}
84 changes: 54 additions & 30 deletions src/routes/file.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use actix_web::{
get, post,
get,
http::header::HeaderValue,
post,
web::{self, Bytes, Data},
HttpRequest, HttpResponse, Responder,
};
Expand Down Expand Up @@ -47,6 +49,11 @@ async fn upload(bytes: Bytes, req: HttpRequest, data: Data<AppData>) -> impl Res
}

let user = user.unwrap();
let encrypted = match req.headers().get("x-encrypted") {
Some(header) => header == HeaderValue::from_static("true"),
None => true,
};

let file_size = bytes.len() as i64;
if user.used + file_size > user.quota {
return HttpResponse::PayloadTooLarge().body("Quota exceeded");
Expand All @@ -70,27 +77,28 @@ async fn upload(bytes: Bytes, req: HttpRequest, data: Data<AppData>) -> impl Res
let file_extension = file_type.split("/").last().unwrap();
let file_hash = format!("{:x}", Sha3_512::digest(&bytes));

let cipher = Cipher::default();
let encrypted_bytes = cipher.encrypt(&bytes);
let encoded = cipher.to_base64();

data.storage
.save(String::from(&uuid), &encrypted_bytes)
.await
.unwrap();

sqlx::query(
"INSERT INTO files (uuid, name, type, hash, size, user_id) VALUES ($1, $2, $3, $4, $5, $6)",
)
.bind(&uuid)
.bind(file_name)
.bind(file_type)
.bind(&file_hash)
.bind(file_size)
.bind(user.id)
.execute(&data.pool)
.await
.unwrap();
let file_query = sqlx::query("INSERT INTO files (uuid, name, type, hash, size, user_id, encrypted) VALUES ($1, $2, $3, $4, $5, $6, $7)").bind(&uuid).bind(file_name).bind(file_type).bind(&file_hash).bind(file_size).bind(user.id);
let (key, nonce) = if encrypted {
let cipher = Cipher::default();
let encrypted_bytes = cipher.encrypt(&bytes);
let encoded = cipher.to_base64();

data.storage
.save(String::from(&uuid), &encrypted_bytes)
.await
.unwrap();

file_query.bind(true).execute(&data.pool).await.unwrap();
(encoded.0, encoded.1)
} else {
data.storage
.save(String::from(&uuid), &bytes)
.await
.unwrap();

file_query.bind(false).execute(&data.pool).await.unwrap();
(String::new(), String::new())
};

sqlx::query("UPDATE users SET used = used + $1 WHERE id = $2")
.bind(file_size)
Expand All @@ -102,8 +110,8 @@ async fn upload(bytes: Bytes, req: HttpRequest, data: Data<AppData>) -> impl Res
HttpResponse::Ok().json(UploadResponse {
id: String::from(&uuid),
ext: String::from(file_extension),
key: encoded.0,
nonce: encoded.1,
key,
nonce,
})
}

Expand Down Expand Up @@ -137,9 +145,17 @@ async fn download(

let file = file.unwrap();

let cipher = Cipher::from_base64(&info.key, &info.nonce);
let encrypted_bytes = data.storage.load(id).await.unwrap();
let bytes = cipher.decrypt(&encrypted_bytes);
if file.encrypted && (info.key.is_empty() || info.nonce.is_empty()) {
return HttpResponse::BadRequest().body("Missing decryption key or nonce");
}

let bytes = if file.encrypted {
let cipher = Cipher::from_base64(&info.key, &info.nonce);
let encrypted_bytes = data.storage.load(id).await.unwrap();
cipher.decrypt(&encrypted_bytes)
} else {
data.storage.load(id).await.unwrap()
};

HttpResponse::Ok()
.append_header(("content-disposition", format!("filename=\"{}\"", file.name)))
Expand Down Expand Up @@ -194,9 +210,17 @@ async fn delete(
return HttpResponse::Unauthorized().body("Invalid API key");
}

let cipher = Cipher::from_base64(&info.key, &info.nonce);
let encrypted_bytes = data.storage.load(id).await.unwrap();
let valid = cipher.verify(&encrypted_bytes);
if file.encrypted && (info.key.is_empty() || info.nonce.is_empty()) {
return HttpResponse::BadRequest().body("Missing decryption key or nonce");
}

let valid = if file.encrypted {
let cipher = Cipher::from_base64(&info.key, &info.nonce);
let encrypted_bytes = data.storage.load(id).await.unwrap();
cipher.verify(&encrypted_bytes)
} else {
true
};

if !valid {
return HttpResponse::Unauthorized().body("Invalid decryption key or nonce");
Expand Down
4 changes: 2 additions & 2 deletions src/routes/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
pub mod user;
pub mod file;
pub mod gen;
pub mod gen;
pub mod user;

0 comments on commit 4697443

Please sign in to comment.