add beginnings of admin panel
This commit is contained in:
parent
61653794e1
commit
920b28f5f5
7 changed files with 292 additions and 5 deletions
84
src/main.rs
84
src/main.rs
|
@ -4,20 +4,23 @@ mod store;
|
|||
mod upload;
|
||||
mod zip;
|
||||
|
||||
use std::{fmt::Debug, path::PathBuf, str::FromStr};
|
||||
use std::{fmt::Debug, path::PathBuf, str::FromStr, ops::Deref};
|
||||
|
||||
use actix_http::StatusCode;
|
||||
use actix_session::{SessionMiddleware, storage::CookieSessionStore, Session};
|
||||
use actix_web::{
|
||||
error::InternalError, get, middleware::Logger, post, web, App, HttpRequest, HttpResponse,
|
||||
HttpServer, Responder,
|
||||
HttpServer, Responder, cookie,
|
||||
};
|
||||
use actix_web_actors::ws;
|
||||
use argon2::{Argon2, PasswordVerifier};
|
||||
use askama_actix::{Template, TemplateToResponse};
|
||||
use bytesize::ByteSize;
|
||||
use log::{error, warn};
|
||||
use password_hash::PasswordHashString;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use state::StateDb;
|
||||
use store::StoredFile;
|
||||
use store::{StoredFile, StoredFiles};
|
||||
use tokio::fs::File;
|
||||
|
||||
const APP_NAME: &str = "transbeam";
|
||||
|
@ -40,6 +43,7 @@ struct Config {
|
|||
reverse_proxy: bool,
|
||||
mnemonic_codes: bool,
|
||||
cachebuster: String,
|
||||
admin_password_hash: PasswordHashString,
|
||||
}
|
||||
|
||||
pub fn get_ip_addr(req: &HttpRequest, reverse_proxy: bool) -> String {
|
||||
|
@ -72,6 +76,67 @@ async fn index(data: web::Data<AppData>) -> impl Responder {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "admin/signed_out.html")]
|
||||
struct SignedOutAdminPage {
|
||||
cachebuster: String,
|
||||
base_url: String,
|
||||
incorrect_password: bool,
|
||||
}
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "admin/signed_in.html")]
|
||||
struct AdminPage<'a> {
|
||||
cachebuster: String,
|
||||
base_url: String,
|
||||
stored_files: &'a StoredFiles,
|
||||
}
|
||||
|
||||
#[get("/admin")]
|
||||
async fn admin_panel(data: web::Data<AppData>, session: Session) -> actix_web::Result<HttpResponse> {
|
||||
if let Some(true) = session.get::<bool>("admin")? {
|
||||
Ok(AdminPage {
|
||||
cachebuster: data.config.cachebuster.clone(),
|
||||
base_url: data.config.base_url.clone(),
|
||||
stored_files: data.state.read().await.deref(),
|
||||
}.to_response())
|
||||
} else {
|
||||
Ok(SignedOutAdminPage {
|
||||
cachebuster: data.config.cachebuster.clone(),
|
||||
base_url: data.config.base_url.clone(),
|
||||
incorrect_password: false,
|
||||
}.to_response())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct AdminPanelSignin {
|
||||
password: String,
|
||||
}
|
||||
|
||||
#[post("/admin")]
|
||||
async fn admin_signin(req: HttpRequest, data: web::Data<AppData>, form: web::Form<AdminPanelSignin>, session: Session) -> actix_web::Result<HttpResponse> {
|
||||
if Argon2::default().verify_password(form.password.as_bytes(), &data.config.admin_password_hash.password_hash()).is_ok() {
|
||||
session.insert("admin", true)?;
|
||||
Ok(AdminPage {
|
||||
cachebuster: data.config.cachebuster.clone(),
|
||||
base_url: data.config.base_url.clone(),
|
||||
stored_files: data.state.read().await.deref(),
|
||||
}.to_response())
|
||||
} else {
|
||||
let ip_addr = get_ip_addr(&req, data.config.reverse_proxy);
|
||||
log_auth_failure(&ip_addr);
|
||||
let mut resp = SignedOutAdminPage {
|
||||
cachebuster: data.config.cachebuster.clone(),
|
||||
base_url: data.config.base_url.clone(),
|
||||
incorrect_password: true,
|
||||
}.to_response();
|
||||
*resp.status_mut() = StatusCode::FORBIDDEN;
|
||||
|
||||
Err(InternalError::from_response("Incorrect password", resp).into())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct DownloadRequest {
|
||||
code: String,
|
||||
|
@ -307,6 +372,15 @@ async fn main() -> std::io::Result<()> {
|
|||
env_or::<ByteSize>("TRANSBEAM_MAX_STORAGE_SIZE", ByteSize(64 * bytesize::GB)).as_u64();
|
||||
let upload_password: String = env_or_panic("TRANSBEAM_UPLOAD_PASSWORD");
|
||||
let cachebuster: String = env_or_else("TRANSBEAM_CACHEBUSTER", String::new);
|
||||
let admin_password_hash: PasswordHashString = env_or_panic("TRANSBEAM_ADMIN_PASSWORD_HASH");
|
||||
|
||||
let cookie_secret_base64: String = env_or_panic("TRANSBEAM_COOKIE_SECRET");
|
||||
let cookie_key = cookie::Key::from(
|
||||
&base64::decode(&cookie_secret_base64)
|
||||
.unwrap_or_else(
|
||||
|_| panic!("Value {} for TRANSBEAM_COOKIE_SECRET is not valid base64", cookie_secret_base64)
|
||||
)
|
||||
);
|
||||
|
||||
let state_file: PathBuf = match std::env::var("TRANSBEAM_STATE_FILE") {
|
||||
Ok(v) => v
|
||||
|
@ -336,6 +410,7 @@ async fn main() -> std::io::Result<()> {
|
|||
reverse_proxy,
|
||||
mnemonic_codes,
|
||||
cachebuster,
|
||||
admin_password_hash,
|
||||
},
|
||||
});
|
||||
data.cleanup().await?;
|
||||
|
@ -349,12 +424,15 @@ async fn main() -> std::io::Result<()> {
|
|||
} else {
|
||||
Logger::default()
|
||||
})
|
||||
.wrap(SessionMiddleware::new(CookieSessionStore::default(), cookie_key.clone()))
|
||||
.service(index)
|
||||
.service(handle_download)
|
||||
.service(download_info)
|
||||
.service(handle_upload)
|
||||
.service(check_upload_password)
|
||||
.service(upload_limits)
|
||||
.service(admin_panel)
|
||||
.service(admin_signin)
|
||||
.service(actix_files::Files::new("/", static_dir.clone()))
|
||||
});
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue