rudimentary optional authentication for mutating operations
parent
22afee0e16
commit
5bb36e9ec6
|
@ -1361,6 +1361,20 @@ dependencies = [
|
|||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jsonwebtoken"
|
||||
version = "8.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "012bb02250fdd38faa5feee63235f7a459974440b9b57593822414c31f92839e"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"pem",
|
||||
"ring",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"simple_asn1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kamadak-exif"
|
||||
version = "0.5.4"
|
||||
|
@ -1745,6 +1759,17 @@ dependencies = [
|
|||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-bigint"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.44"
|
||||
|
@ -1796,6 +1821,15 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_threads"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97ba99ba6393e2c3734791401b66902d981cb03bf190af674ca69949b6d5fb15"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.9.0"
|
||||
|
@ -1862,6 +1896,15 @@ dependencies = [
|
|||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pem"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e9a3b09a20e374558580a4914d3b7d89bd61b954a5a5e1dcbea98753addb1947"
|
||||
dependencies = [
|
||||
"base64",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.1.0"
|
||||
|
@ -2034,6 +2077,15 @@ version = "1.2.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
|
||||
|
||||
[[package]]
|
||||
name = "quickcheck"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "588f6378e4dd99458b60ec275b4477add41ce4fa9f64dcba6f15adccb19b50d6"
|
||||
dependencies = [
|
||||
"rand 0.8.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.14"
|
||||
|
@ -2231,6 +2283,21 @@ dependencies = [
|
|||
"quick-error",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ring"
|
||||
version = "0.16.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"spin",
|
||||
"untrusted",
|
||||
"web-sys",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.2.3"
|
||||
|
@ -2390,6 +2457,18 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "simple_asn1"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a762b1c38b9b990c694b9c2f8abe3372ce6a9ceaae6bca39cfc46e054f45745"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"num-traits",
|
||||
"thiserror",
|
||||
"time 0.3.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "siphasher"
|
||||
version = "0.3.9"
|
||||
|
@ -2429,6 +2508,12 @@ dependencies = [
|
|||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spin"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
|
||||
|
||||
[[package]]
|
||||
name = "standback"
|
||||
version = "0.2.17"
|
||||
|
@ -2633,11 +2718,24 @@ dependencies = [
|
|||
"libc",
|
||||
"standback",
|
||||
"stdweb",
|
||||
"time-macros",
|
||||
"time-macros 0.1.1",
|
||||
"version_check 0.9.4",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "004cbc98f30fa233c61a38bc77e96a9106e65c88f2d3bef182ae952027e5753d"
|
||||
dependencies = [
|
||||
"itoa 1.0.1",
|
||||
"libc",
|
||||
"num_threads",
|
||||
"quickcheck",
|
||||
"time-macros 0.2.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.1.1"
|
||||
|
@ -2648,6 +2746,12 @@ dependencies = [
|
|||
"time-macros-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "25eb0ca3468fc0acc11828786797f6ef9aa1555e4a211a60d64cc8e4d1be47d6"
|
||||
|
||||
[[package]]
|
||||
name = "time-macros-impl"
|
||||
version = "0.1.2"
|
||||
|
@ -2899,6 +3003,12 @@ version = "0.7.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d86a8dc7f45e4c1b0d30e43038c38f274e77af056aa5f74b93c2cf9eb3c1c836"
|
||||
|
||||
[[package]]
|
||||
name = "untrusted"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
|
||||
|
||||
[[package]]
|
||||
name = "upend"
|
||||
version = "0.0.61"
|
||||
|
@ -2923,6 +3033,7 @@ dependencies = [
|
|||
"id3",
|
||||
"image",
|
||||
"is_executable",
|
||||
"jsonwebtoken",
|
||||
"kamadak-exif",
|
||||
"lazy_static",
|
||||
"lexpr",
|
||||
|
@ -2933,6 +3044,7 @@ dependencies = [
|
|||
"nonempty",
|
||||
"once_cell",
|
||||
"opener",
|
||||
"rand 0.8.4",
|
||||
"rayon",
|
||||
"regex",
|
||||
"serde",
|
||||
|
|
|
@ -38,6 +38,7 @@ actix-files = "^0.5"
|
|||
actix-rt = "^2.0"
|
||||
actix-web = "^3.3"
|
||||
actix_derive = "^0.5"
|
||||
jsonwebtoken = "8"
|
||||
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
|
@ -46,13 +47,16 @@ lexpr = "0.2.6"
|
|||
regex = "1"
|
||||
|
||||
bs58 = "^0.4"
|
||||
filebuffer = "0.4.0"
|
||||
tiny-keccak = { version = "2.0", features = ["k12"] }
|
||||
unsigned-varint = { version = "^0", features = ["std"] }
|
||||
uuid = { version = "0.8", features = ["v4"] }
|
||||
|
||||
filebuffer = "0.4.0"
|
||||
tempfile = "^3.2.0"
|
||||
walkdir = "2"
|
||||
|
||||
rand = "0.8"
|
||||
|
||||
mime = "^0.3.16"
|
||||
tree_magic_mini = "3.0.2"
|
||||
|
||||
|
|
33
src/main.rs
33
src/main.rs
|
@ -12,6 +12,7 @@ use actix_web::{middleware, App, HttpServer};
|
|||
use anyhow::Result;
|
||||
use clap::{App as ClapApp, Arg};
|
||||
use log::{debug, info, warn};
|
||||
use rand::{thread_rng, Rng};
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::{
|
||||
|
@ -85,6 +86,20 @@ fn main() -> Result<()> {
|
|||
.takes_value(true)
|
||||
.long("name")
|
||||
.help("Name of the vault."),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("SECRET")
|
||||
.takes_value(true)
|
||||
.long("secret")
|
||||
.env("UPEND_SECRET")
|
||||
.help("Secret to use for authentication."),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("KEY")
|
||||
.takes_value(true)
|
||||
.long("key")
|
||||
.env("UPEND_KEY")
|
||||
.help("Authentication key users must supply."),
|
||||
);
|
||||
|
||||
let matches = app.get_matches();
|
||||
|
@ -142,6 +157,21 @@ fn main() -> Result<()> {
|
|||
.parse()
|
||||
.expect("Incorrect bind format.");
|
||||
|
||||
let secret = matches
|
||||
.value_of("SECRET")
|
||||
.map(String::from)
|
||||
.unwrap_or_else(|| {
|
||||
warn!("No secret supplied, generating one at random.");
|
||||
|
||||
thread_rng()
|
||||
.sample_iter(&rand::distributions::Alphanumeric)
|
||||
.take(32)
|
||||
.map(char::from)
|
||||
.collect()
|
||||
});
|
||||
|
||||
let key = matches.value_of("KEY").map(String::from);
|
||||
|
||||
let state = routes::State {
|
||||
upend: upend.clone(),
|
||||
vault_name: Some(
|
||||
|
@ -160,6 +190,8 @@ fn main() -> Result<()> {
|
|||
job_container: job_container.clone(),
|
||||
preview_store,
|
||||
desktop_enabled,
|
||||
secret,
|
||||
key,
|
||||
};
|
||||
|
||||
// Start HTTP server
|
||||
|
@ -175,6 +207,7 @@ fn main() -> Result<()> {
|
|||
.app_data(actix_web::web::PayloadConfig::new(4_294_967_296))
|
||||
.data(state.clone())
|
||||
.wrap(middleware::Logger::default().exclude("/api/jobs"))
|
||||
.service(routes::login)
|
||||
.service(routes::get_raw)
|
||||
.service(routes::get_thumbnail)
|
||||
.service(routes::get_query)
|
||||
|
|
|
@ -12,14 +12,19 @@ use crate::util::hash::{b58_decode, b58_encode, Hashable};
|
|||
use crate::util::jobs::JobContainer;
|
||||
use actix_files::NamedFile;
|
||||
use actix_multipart::Multipart;
|
||||
use actix_web::error::{ErrorBadRequest, ErrorInternalServerError, ErrorNotFound};
|
||||
use actix_web::http::header::{CacheControl, CacheDirective, ContentDisposition, DispositionType};
|
||||
use actix_web::error::{
|
||||
ErrorBadRequest, ErrorInternalServerError, ErrorNotFound, ErrorUnauthorized,
|
||||
};
|
||||
use actix_web::{delete, error, get, post, put, web, Either, Error, HttpResponse};
|
||||
use actix_web::{http, Responder};
|
||||
use actix_web::{
|
||||
http::header::{CacheControl, CacheDirective, ContentDisposition, DispositionType},
|
||||
HttpRequest,
|
||||
};
|
||||
use anyhow::{anyhow, Result};
|
||||
use futures_util::TryStreamExt;
|
||||
use log::{debug, info, trace, warn};
|
||||
use serde::Deserialize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::json;
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
use std::fs;
|
||||
|
@ -41,6 +46,69 @@ pub struct State {
|
|||
pub job_container: JobContainer,
|
||||
pub preview_store: Option<Arc<PreviewStore>>,
|
||||
pub desktop_enabled: bool,
|
||||
pub secret: String,
|
||||
pub key: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct JwtClaims {
|
||||
exp: usize,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct LoginRequest {
|
||||
key: String,
|
||||
}
|
||||
|
||||
#[post("/api/auth/login")]
|
||||
pub async fn login(
|
||||
state: web::Data<State>,
|
||||
payload: web::Json<LoginRequest>,
|
||||
) -> Result<HttpResponse, Error> {
|
||||
if state.key.is_none() || Some(&payload.key) == state.key.as_ref() {
|
||||
let claims = JwtClaims {
|
||||
exp: (SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.map_err(ErrorInternalServerError)?
|
||||
.as_secs()
|
||||
+ 7 * 24 * 60 * 60) as usize,
|
||||
};
|
||||
|
||||
let token = jsonwebtoken::encode(
|
||||
&jsonwebtoken::Header::default(),
|
||||
&claims,
|
||||
&jsonwebtoken::EncodingKey::from_secret(state.secret.as_ref()),
|
||||
)
|
||||
.map_err(ErrorInternalServerError)?;
|
||||
|
||||
Ok(HttpResponse::Ok().json(json!({ "token": token })))
|
||||
} else {
|
||||
Err(ErrorUnauthorized("Incorrect token."))
|
||||
}
|
||||
}
|
||||
|
||||
fn check_auth(req: &HttpRequest, state: &State) -> Result<(), actix_web::Error> {
|
||||
if let Some(key) = &state.key {
|
||||
if let Some(auth_header) = req.headers().get("Authorization") {
|
||||
let auth_header = auth_header.to_str().map_err(|err| {
|
||||
ErrorBadRequest(format!("Invalid value in Authorization header: {err:?}"))
|
||||
})?;
|
||||
|
||||
let token = jsonwebtoken::decode::<JwtClaims>(
|
||||
auth_header,
|
||||
&jsonwebtoken::DecodingKey::from_secret(key.as_ref()),
|
||||
&jsonwebtoken::Validation::default(),
|
||||
);
|
||||
|
||||
token
|
||||
.map(|_| ())
|
||||
.map_err(|err| ErrorUnauthorized(format!("Invalid token: {err:?}")))
|
||||
} else {
|
||||
Err(ErrorUnauthorized("Authorization required."))
|
||||
}
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
|
@ -303,9 +371,12 @@ impl TryInto<Address> for InAddress {
|
|||
|
||||
#[put("/api/obj")]
|
||||
pub async fn put_object(
|
||||
req: HttpRequest,
|
||||
state: web::Data<State>,
|
||||
payload: Either<web::Json<InEntry>, Multipart>,
|
||||
) -> Result<HttpResponse, Error> {
|
||||
check_auth(&req, &state)?;
|
||||
|
||||
let (entry_address, entity_address) = match payload {
|
||||
Either::A(in_entry) => {
|
||||
let connection = state.upend.connection().map_err(ErrorInternalServerError)?;
|
||||
|
@ -494,10 +565,12 @@ pub async fn put_object(
|
|||
|
||||
#[put("/api/obj/{address}/{attribute}")]
|
||||
pub async fn put_object_attribute(
|
||||
req: HttpRequest,
|
||||
state: web::Data<State>,
|
||||
web::Path((address, attribute)): web::Path<(Address, String)>,
|
||||
value: web::Json<EntryValue>,
|
||||
) -> Result<HttpResponse, Error> {
|
||||
check_auth(&req, &state)?;
|
||||
let connection = state.upend.connection().map_err(ErrorInternalServerError)?;
|
||||
|
||||
let new_address = web::block(move || {
|
||||
|
@ -526,9 +599,12 @@ pub async fn put_object_attribute(
|
|||
|
||||
#[delete("/api/obj/{address_str}")]
|
||||
pub async fn delete_object(
|
||||
req: HttpRequest,
|
||||
state: web::Data<State>,
|
||||
address: web::Path<Address>,
|
||||
) -> Result<HttpResponse, Error> {
|
||||
check_auth(&req, &state)?;
|
||||
|
||||
let connection = state.upend.connection().map_err(ErrorInternalServerError)?;
|
||||
let _ = web::block(move || connection.remove_object(address.into_inner()))
|
||||
.await
|
||||
|
@ -649,9 +725,12 @@ pub struct RescanRequest {
|
|||
|
||||
#[post("/api/refresh")]
|
||||
pub async fn api_refresh(
|
||||
req: HttpRequest,
|
||||
state: web::Data<State>,
|
||||
web::Query(query): web::Query<RescanRequest>,
|
||||
) -> Result<HttpResponse, Error> {
|
||||
check_auth(&req, &state)?;
|
||||
|
||||
block_background::<_, _, anyhow::Error>(move || {
|
||||
let _ = crate::filesystem::rescan_vault(
|
||||
state.upend.clone(),
|
||||
|
|
Loading…
Reference in New Issue