From 309a78d246ff175eaf12f6a7d4e9d70845b9edda Mon Sep 17 00:00:00 2001 From: KunoiSayami <46131041+KunoiSayami@users.noreply.github.com> Date: Mon, 10 May 2021 16:09:19 +0800 Subject: feat: Add reset database subcommand * fix: Fix CI build failure --- src/database.rs | 8 +++ src/datastructures.rs | 8 +-- src/main.rs | 163 +++++++++++++++++++++++++++++++------------------- 3 files changed, 115 insertions(+), 64 deletions(-) (limited to 'src') diff --git a/src/database.rs b/src/database.rs index 0998b2e..a9af02b 100644 --- a/src/database.rs +++ b/src/database.rs @@ -36,6 +36,14 @@ pub mod v1 { INSERT INTO "auth_meta" VALUES ('version', '1'); "#; + pub const DROP_TABLES: &str = r#" + + DROP TABLE "accounts"; + + DROP TABLE "auth_meta"; + + "#; + pub const VERSION: &str = "1"; } pub use v1 as current; diff --git a/src/datastructures.rs b/src/datastructures.rs index bb91cfb..319446d 100644 --- a/src/datastructures.rs +++ b/src/datastructures.rs @@ -35,9 +35,9 @@ use rand_core::OsRng; const DEFAULT_CONFIG_LOCATION: &str = "/etc/cgitrc"; const DEFAULT_COOKIE_TTL: u64 = 1200; const DEFAULT_DATABASE_LOCATION: &str = "/etc/cgit/auth.db"; -pub const CACHE_DIR: &str = "/var/cache/cgit"; +//pub const CACHE_DIR: &str = "/var/cache/cgit"; pub type RandIntType = u32; -pub const MINIMUM_SECRET_LENGTH: usize = 8; +//pub const MINIMUM_SECRET_LENGTH: usize = 8; pub fn get_current_timestamp() -> u64 { let start = std::time::SystemTime::now(); @@ -100,7 +100,7 @@ impl Config { let mut cookie_ttl: u64 = DEFAULT_COOKIE_TTL; let mut database: &str = "/etc/cgit/auth.db"; let mut bypass_root: bool = false; - let mut secret: &str = ""; + //let mut secret: &str = ""; for line in file.lines() { let line = line.trim(); if !line.contains('=') || !line.starts_with("cgit-simple-auth-") { @@ -117,7 +117,7 @@ impl Config { "cookie-ttl" => cookie_ttl = value.parse().unwrap_or(DEFAULT_COOKIE_TTL), "database" => database = value, "bypass-root" => bypass_root = value.to_lowercase().eq("true"), - "secret" => secret = value, + //"secret" => secret = value, _ => {} } } diff --git a/src/main.rs b/src/main.rs index 0f5fd48..76c7fa3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,3 @@ -#![feature(array_methods)] /* ** Copyright (C) 2021 KunoiSayami ** @@ -31,7 +30,7 @@ use log4rs::config::{Appender, Root}; use log4rs::encode::pattern::PatternEncoder; use redis::AsyncCommands; use serde::Serialize; -use sqlx::Connection; +use sqlx::{Connection, ConnectOptions, SqliteConnection}; use std::env; use std::io::{stdin, Read}; use std::result::Result::Ok; @@ -39,6 +38,7 @@ use tokio_stream::StreamExt as _; use argon2::{ password_hash::{PasswordHash}, }; +use std::str::FromStr; const COOKIE_LENGTH: usize = 45; @@ -100,15 +100,10 @@ async fn cmd_init(cfg: Config) -> Result<()> { } async fn verify_login(cfg: &Config, data: &FormData) -> Result { - // TODO: use timestamp to mark file diff - // or copy in init process - let database_file_name = std::path::Path::new(datastructures::CACHE_DIR).join( - std::path::Path::new(cfg.get_database_location()) - .file_name() - .unwrap(), - ); - std::fs::copy(cfg.get_database_location(), database_file_name.clone())?; - let mut conn = sqlx::SqliteConnection::connect(database_file_name.to_str().unwrap()).await?; + let mut conn = sqlx::sqlite::SqliteConnectOptions::from_str(cfg.get_database_location())? + .read_only(true) + .log_statements(log::LevelFilter::Trace) + .connect().await?; let (passwd_hash,) = sqlx::query_as::<_, (String, )>(r#"SELECT "password" FROM "accounts" WHERE "user" = ?"#) .bind(data.get_user()) .fetch_one(&mut conn) @@ -121,6 +116,7 @@ async fn verify_login(cfg: &Config, data: &FormData) -> Result { async fn cmd_authenticate_post(matches: &ArgMatches<'_>, cfg: Config) -> Result<()> { // Read stdin from upstream. let mut buffer = String::new(); + // TODO: override it that can test function from cargo test stdin().read_to_string(&mut buffer)?; log::debug!("{}", buffer); let data = datastructures::FormData::from(buffer); @@ -177,7 +173,7 @@ pub struct Meta<'a> { // Processing the `body` called by cgit. -async fn cmd_body(matches: &ArgMatches<'_>, cfg: Config) { +async fn cmd_body(matches: &ArgMatches<'_>, _cfg: Config) { let source = include_str!("authentication_page.html"); let handlebars = Handlebars::new(); let meta = Meta { @@ -197,8 +193,8 @@ async fn cmd_add_user(matches: &ArgMatches<'_>, cfg: Config) -> Result<()> { return Err(anyhow::Error::msg("Invalid user or password")); } - if user.len() > 20 { - return Err(anyhow::Error::msg("Username length should less than 20")) + if user.len() >= 20 { + return Err(anyhow::Error::msg("Username length should less than 21")) } let mut conn = sqlx::SqliteConnection::connect(cfg.get_database_location()).await?; @@ -274,6 +270,26 @@ async fn cmd_delete_user(matches: &ArgMatches<'_>, cfg: Config) -> Result<()> { Ok(()) } +async fn cmd_reset_database(matches: &ArgMatches<'_>, cfg: Config) -> Result<()> { + if !matches.is_present("confirm") { + return Err(anyhow::Error::msg("Please add --confirm argument to process reset")) + } + + let mut conn = SqliteConnection::connect(cfg.get_database_location()).await?; + + sqlx::query(database::current::DROP_TABLES) + .execute(&mut conn) + .await?; + + sqlx::query(database::current::CREATE_TABLES) + .execute(&mut conn) + .await?; + + println!("Reset database successfully"); + + Ok(()) +} + async fn async_main(arg_matches: ArgMatches<'_>, cfg: Config) -> Result { match arg_matches.subcommand() { ("authenticate-cookie", Some(matches)) => { @@ -302,44 +318,15 @@ async fn async_main(arg_matches: ArgMatches<'_>, cfg: Config) -> Result { ("deluser", Some(matches)) => { cmd_delete_user(matches, cfg).await?; } + ("reset", Some(matches)) => { + cmd_reset_database(matches, cfg).await?; + } _ => {} } Ok(0) } -fn main() -> Result<()> { - let logfile = FileAppender::builder() - .encoder(Box::new(PatternEncoder::new( - "{d(%Y-%m-%d %H:%M:%S)}- {h({l})} - {m}{n}", - ))) - .build(option_env!("RUST_LOG_FILE").unwrap_or("/tmp/auth.log"))?; - - let config = log4rs::Config::builder() - .appender(Appender::builder().build("logfile", Box::new(logfile))) - .logger(log4rs::config::Logger::builder().build("sqlx::query", log::LevelFilter::Warn)) - .logger( - log4rs::config::Logger::builder().build("handlebars::render", log::LevelFilter::Warn), - ) - .logger( - log4rs::config::Logger::builder().build("handlebars::context", log::LevelFilter::Warn), - ) - .build( - Root::builder() - .appender("logfile") - .build(log::LevelFilter::Debug), - )?; - - log4rs::init_config(config)?; - //simple_logging::log_to_file("/tmp/auth.log", log::LevelFilter::Debug)?; - - log::debug!( - "{}", - env::args() - .enumerate() - .map(|(nth, arg)| format!("[{}]={}", nth, arg)) - .collect::>() - .join(" ") - ); +fn process_arguments(arguments: Option>) -> Result<()> { // Sub-arguments for each command, see cgi defines. let sub_args = &[ @@ -356,7 +343,7 @@ fn main() -> Result<()> { Arg::with_name("login-url").required(true), ]; - let matches = App::new("Simple Authentication Filter for cgit") + let app = App::new("Simple Authentication Filter for cgit") .version(env!("CARGO_PKG_VERSION")) .subcommand( SubCommand::with_name("authenticate-cookie") @@ -386,7 +373,18 @@ fn main() -> Result<()> { .about("Delete user from database") .arg(Arg::with_name("user").required(true)) ) - .get_matches(); + .subcommand( + SubCommand::with_name("reset") + .about("Reset database") + .arg(Arg::with_name("confirm").long("confirm")) + ); + + let matches = if let Some(args) = arguments { + app.get_matches_from(args) + } else { + app.get_matches() + }; + // Load filter configurations let cfg = Config::new(); @@ -403,19 +401,54 @@ fn main() -> Result<()> { Ok(()) } -mod test { - const PASSWORD: &str = "hunter2"; - const ARGON2_HASH: &str = "$argon2id$v=19$m=4096,t=3,p=1$szYDnoQSVPmXq+RD2LneBw$fRETH//iCQuIX+SgjYPdZ9iIbM8gEy9fBjTJ/KFFJNM"; - use argon2::{ - password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString}, - Argon2 - }; - use rand_core::OsRng; +fn main() -> Result<()> { + let logfile = FileAppender::builder() + .encoder(Box::new(PatternEncoder::new( + "{d(%Y-%m-%d %H:%M:%S)}- {h({l})} - {m}{n}", + ))) + .build(option_env!("RUST_LOG_FILE").unwrap_or("/tmp/auth.log"))?; + let config = log4rs::Config::builder() + .appender(Appender::builder().build("logfile", Box::new(logfile))) + //.logger(log4rs::config::Logger::builder().build("sqlx::query", log::LevelFilter::Warn)) + .logger( + log4rs::config::Logger::builder().build("handlebars::render", log::LevelFilter::Warn), + ) + .logger( + log4rs::config::Logger::builder().build("handlebars::context", log::LevelFilter::Warn), + ) + .build( + Root::builder() + .appender("logfile") + .build(log::LevelFilter::Debug), + )?; + + log4rs::init_config(config)?; + log::debug!( + "{}", + env::args() + .enumerate() + .map(|(nth, arg)| format!("[{}]={}", nth, arg)) + .collect::>() + .join(" ") + ); + + process_arguments(None)?; + + Ok(()) +} + +mod test { + use crate::process_arguments; #[test] fn test_argon2() { - let passwd = PASSWORD.as_bytes(); + use argon2::{ + password_hash::{PasswordHasher, SaltString}, + Argon2 + }; + use rand_core::OsRng; + let passwd = b"hunter2"; let salt = SaltString::generate(&mut OsRng); let argon2 = Argon2::default(); @@ -426,10 +459,20 @@ mod test { #[test] fn test_argon2_verify() { - let passwd = PASSWORD.as_bytes(); - let parsed_hash = PasswordHash::new(ARGON2_HASH).unwrap(); + use argon2::{ + password_hash::{PasswordHash, PasswordVerifier}, + Argon2 + }; + let passwd = b"hunter2"; + let parsed_hash = PasswordHash::new("$argon2id$v=19$m=4096,t=3,p=1$szYDnoQSVPmXq+RD2LneBw$fRETH//iCQuIX+SgjYPdZ9iIbM8gEy9fBjTJ/KFFJNM").unwrap(); let argon2 = Argon2::default(); assert!(argon2.verify_password(passwd, &parsed_hash).is_ok()) } + + #[cfg(unix)] + #[allow(dead_code)] + fn test_auth_post() { + process_arguments(Some(vec!["cgit-simple-authentication", "authenticate-post", "", "POST", "p=login", "https://git.example.com/?p=login", "/", "git.example.com", "", "", "login", "/?p=login", "/?p=login"])).unwrap(); + } } -- cgit v1.3.1