Hash admin passwords
This commit is contained in:
parent
980a85d41b
commit
86495543ce
7 changed files with 106 additions and 7 deletions
|
|
@ -53,6 +53,9 @@ pub enum MainSubcommand {
|
|||
|
||||
/// Start server
|
||||
Start(StartOpt),
|
||||
|
||||
/// Add admin password
|
||||
Psw,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Parser)]
|
||||
|
|
|
|||
|
|
@ -110,3 +110,9 @@ pub fn read_config(dir: &Path) -> Config {
|
|||
.expect("Bad JSON in config file")
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_config(dir: &Path, config: &Config) {
|
||||
let path = dir.join(CONFIG_FILE);
|
||||
std::fs::write(path, toml_edit::easy::to_string_pretty(&config).unwrap())
|
||||
.expect("Cannot write config file");
|
||||
}
|
||||
|
|
|
|||
16
src/main.rs
16
src/main.rs
|
|
@ -6,6 +6,10 @@ mod queries;
|
|||
mod server;
|
||||
mod templates;
|
||||
|
||||
use argon2::{
|
||||
password_hash::{rand_core::OsRng, PasswordHasher, SaltString},
|
||||
Argon2,
|
||||
};
|
||||
use clap::Parser;
|
||||
|
||||
#[async_std::main]
|
||||
|
|
@ -20,6 +24,18 @@ async fn main() {
|
|||
let (config, dbs, templates) = init_all(opt.opt, subopt);
|
||||
server::start_server(config, dbs, templates).await
|
||||
}
|
||||
cli::MainSubcommand::Psw => {
|
||||
let mut config = config::read_config(&opt.opt.dir.0);
|
||||
let password = rpassword::prompt_password("Additional admin password: ").unwrap();
|
||||
let salt = SaltString::generate(&mut OsRng);
|
||||
let argon2 = Argon2::default();
|
||||
let password_hash = argon2
|
||||
.hash_password(password.as_bytes(), &salt)
|
||||
.unwrap()
|
||||
.to_string();
|
||||
config.admin_passwords.push(password_hash);
|
||||
config::write_config(&opt.opt.dir.0, &config);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use crate::{config::*, db::*, helpers, queries::*, templates::*};
|
||||
|
||||
use argon2::{Argon2, PasswordHash, PasswordVerifier};
|
||||
use log::error;
|
||||
use std::sync::Arc;
|
||||
use tera::Context;
|
||||
|
|
@ -54,7 +55,7 @@ async fn serve_comments<'a>(
|
|||
};
|
||||
|
||||
let admin = req.cookie("admin").map_or(false, |psw| {
|
||||
config.admin_passwords.contains(&String::from(psw.value()))
|
||||
check_admin_password_hash(&config, &String::from(psw.value()))
|
||||
});
|
||||
|
||||
let topic_hash = TopicHash::from_topic(topic);
|
||||
|
|
@ -127,9 +128,9 @@ async fn serve_admin<'a>(
|
|||
&dbs.comment_pending
|
||||
.iter()
|
||||
.filter_map(|entry| {
|
||||
let ((_topic_hash, _time, comment_id), ()) = dbg!(entry
|
||||
let ((_topic_hash, _time, comment_id), ()) = entry
|
||||
.map_err(|e| error!("Reading comment_pending: {:?}", e))
|
||||
.ok()?);
|
||||
.ok()?;
|
||||
let comment = dbs
|
||||
.comment
|
||||
.get(&comment_id)
|
||||
|
|
@ -227,7 +228,7 @@ async fn handle_post_admin(
|
|||
dbs: Dbs,
|
||||
) -> tide::Result<tide::Response> {
|
||||
if let Some(psw) = req.cookie("admin") {
|
||||
if config.admin_passwords.contains(&String::from(psw.value())) {
|
||||
if check_admin_password(&config, &String::from(psw.value())).is_some() {
|
||||
match req.body_form::<AdminQuery>().await? {
|
||||
_ => serve_admin(req, config, templates, dbs).await,
|
||||
}
|
||||
|
|
@ -235,11 +236,11 @@ async fn handle_post_admin(
|
|||
serve_admin_login(req, config, templates).await
|
||||
}
|
||||
} else if let AdminQuery::Login(query) = req.body_form::<AdminQuery>().await? {
|
||||
if config.admin_passwords.contains(&query.psw) {
|
||||
if let Some(password_hash) = check_admin_password(&config, &query.psw) {
|
||||
serve_admin(req, config.clone(), templates, dbs)
|
||||
.await
|
||||
.map(|mut r| {
|
||||
let mut cookie = tide::http::Cookie::new("admin", query.psw);
|
||||
let mut cookie = tide::http::Cookie::new("admin", password_hash);
|
||||
cookie.set_http_only(Some(true));
|
||||
cookie.set_path(config.root_url.clone());
|
||||
if let Some(domain) = &config.cookies_domain {
|
||||
|
|
@ -258,3 +259,21 @@ async fn handle_post_admin(
|
|||
serve_admin_login(req, config, templates).await
|
||||
}
|
||||
}
|
||||
|
||||
fn check_admin_password(config: &Config, password: &str) -> Option<String> {
|
||||
let argon2 = Argon2::default();
|
||||
config
|
||||
.admin_passwords
|
||||
.iter()
|
||||
.filter_map(|admin_password| PasswordHash::new(admin_password).ok())
|
||||
.find(|admin_password| {
|
||||
argon2
|
||||
.verify_password(password.as_bytes(), admin_password)
|
||||
.is_ok()
|
||||
})
|
||||
.map(|password_hash| password_hash.to_string())
|
||||
}
|
||||
|
||||
fn check_admin_password_hash(config: &Config, password_hash: &str) -> bool {
|
||||
config.admin_passwords.iter().any(|h| h == password_hash)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue