add beginnings of admin panel

This commit is contained in:
xenofem 2022-11-10 12:41:09 -05:00
parent 61653794e1
commit 920b28f5f5
7 changed files with 292 additions and 5 deletions

View file

@ -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()))
});