switch from docopt to structopt

A couple reasons for this:

* the docopt crate is "unlikely to see significant future evolution",
  and the wider docopt project is "mostly unmaintained at this point".
  clap/structopt is more full-featured, has more natural subcommand
  support, etc.

* it may allow me to shrink the binary (#70). This change alone seems
  to be a slight regression, but it's a step toward getting rid of
  regex, which is pretty large. And I feel less ridiculous now that I
  don't have two parsing crates anyway; prettydiff was pulling in
  structopt.

There are some behavior changes here:

* misc --help output changes and such as you'd expect from switching
  argument-parsing libraries

* I properly used PathBuf and OsString for stuff that theoretically
  could be non-UTF-8. I haven't tested that it actually made any
  difference. I'm also still storing the sample file dirname as "text"
  in the database to avoid causing a diff when not doing a schema
  change.
This commit is contained in:
Scott Lamb 2020-04-17 22:41:55 -07:00
parent 066c086050
commit e8eb764b90
17 changed files with 383 additions and 417 deletions

81
Cargo.lock generated
View File

@ -275,6 +275,7 @@ dependencies = [
"atty",
"bitflags",
"strsim 0.8.0",
"term_size",
"textwrap",
"unicode-width",
"vec_map",
@ -509,18 +510,6 @@ dependencies = [
"winapi 0.3.8",
]
[[package]]
name = "docopt"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f525a586d310c87df72ebcd98009e57f1cc030c8c268305287a476beb653969"
dependencies = [
"lazy_static",
"regex",
"serde",
"strsim 0.9.3",
]
[[package]]
name = "dtoa"
version = "0.4.4"
@ -1282,7 +1271,6 @@ dependencies = [
"bytes",
"cstr",
"cursive",
"docopt",
"failure",
"fnv",
"futures",
@ -1310,6 +1298,7 @@ dependencies = [
"serde",
"serde_json",
"smallvec 1.1.0",
"structopt 0.3.13",
"tempdir",
"time 0.1.42",
"tokio",
@ -1624,7 +1613,7 @@ checksum = "5240be0c9ea1bc7887819a36264cb9475eb71c58749808e5b989c8c1fdc67acf"
dependencies = [
"ansi_term 0.9.0",
"prettytable-rs",
"structopt",
"structopt 0.2.18",
]
[[package]]
@ -1641,6 +1630,32 @@ dependencies = [
"unicode-width",
]
[[package]]
name = "proc-macro-error"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98e9e4b82e0ef281812565ea4751049f1bdcdfccda7d3f459f2e138a40c08678"
dependencies = [
"proc-macro-error-attr",
"proc-macro2 1.0.8",
"quote 1.0.2",
"syn 1.0.14",
"version_check 0.9.1",
]
[[package]]
name = "proc-macro-error-attr"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f5444ead4e9935abd7f27dc51f7e852a0569ac888096d5ec2499470794e2e53"
dependencies = [
"proc-macro2 1.0.8",
"quote 1.0.2",
"syn 1.0.14",
"syn-mid",
"version_check 0.9.1",
]
[[package]]
name = "proc-macro-hack"
version = "0.5.11"
@ -2302,7 +2317,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16c2cdbf9cc375f15d1b4141bc48aeef444806655cd0e904207edc8d68d86ed7"
dependencies = [
"clap",
"structopt-derive",
"structopt-derive 0.2.18",
]
[[package]]
name = "structopt"
version = "0.3.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff6da2e8d107dfd7b74df5ef4d205c6aebee0706c647f6bc6a2d5789905c00fb"
dependencies = [
"clap",
"lazy_static",
"structopt-derive 0.4.6",
]
[[package]]
@ -2317,6 +2343,19 @@ dependencies = [
"syn 0.15.44",
]
[[package]]
name = "structopt-derive"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a489c87c08fbaf12e386665109dd13470dcc9c4583ea3e10dd2b4523e5ebd9ac"
dependencies = [
"heck",
"proc-macro-error",
"proc-macro2 1.0.8",
"quote 1.0.2",
"syn 1.0.14",
]
[[package]]
name = "subtle"
version = "1.0.0"
@ -2345,6 +2384,17 @@ dependencies = [
"unicode-xid 0.2.0",
]
[[package]]
name = "syn-mid"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7be3539f6c128a931cf19dcee741c1af532c7fd387baa739c03dd2e96479338a"
dependencies = [
"proc-macro2 1.0.8",
"quote 1.0.2",
"syn 1.0.14",
]
[[package]]
name = "synstructure"
version = "0.12.3"
@ -2409,6 +2459,7 @@ version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
dependencies = [
"term_size",
"unicode-width",
]

View File

@ -25,7 +25,10 @@ byteorder = "1.0"
cstr = "0.1.7"
cursive = "0.14.0"
db = { package = "moonfire-db", path = "db" }
docopt = "1.0"
#structopt = "0.3.13"
structopt = { version = "0.3.13", features = ["default", "wrap_help"] }
# default = ["suggestions", "color", "vec_map", "derive", "std", "cargo"]
failure = "0.1.1"
ffmpeg = { package = "moonfire-ffmpeg", path = "ffmpeg" }
futures = "0.3"

View File

@ -42,6 +42,7 @@ use rusqlite::{Connection, Transaction, params};
use std::collections::BTreeMap;
use std::fmt;
use std::net::IpAddr;
use std::str::FromStr;
use std::sync::Arc;
lazy_static! {
@ -57,7 +58,7 @@ pub(crate) fn set_test_config() {
Arc::new(libpasta::Config::with_primitive(libpasta::primitives::Bcrypt::new(2)));
}
enum UserFlags {
enum UserFlag {
Disabled = 1,
}
@ -91,7 +92,7 @@ impl User {
}
pub fn has_password(&self) -> bool { self.password_hash.is_some() }
fn disabled(&self) -> bool { (self.flags & UserFlags::Disabled as i32) != 0 }
fn disabled(&self) -> bool { (self.flags & UserFlag::Disabled as i32) != 0 }
}
/// A change to a user.
@ -132,7 +133,7 @@ impl UserChange {
}
pub fn disable(&mut self) {
self.flags |= UserFlags::Disabled as i32;
self.flags |= UserFlag::Disabled as i32;
}
}
@ -194,13 +195,29 @@ impl rusqlite::types::FromSql for FromSqlIpAddr {
}
}
pub enum SessionFlags {
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(i32)]
pub enum SessionFlag {
HttpOnly = 1,
Secure = 2,
SameSite = 4,
SameSiteStrict = 8,
}
impl FromStr for SessionFlag {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"http-only" => Ok(Self::HttpOnly),
"secure" => Ok(Self::Secure),
"same-site" => Ok(Self::SameSite),
"same-site-strict" => Ok(Self::SameSiteStrict),
_ => bail!("No such session flag {:?}", s),
}
}
}
#[derive(Copy, Clone)]
pub enum RevocationReason {
LoggedOut = 1,
@ -209,7 +226,7 @@ pub enum RevocationReason {
#[derive(Debug, Default)]
pub struct Session {
user_id: i32,
flags: i32, // bitmask of SessionFlags enum values
flags: i32, // bitmask of SessionFlag enum values
domain: Option<Vec<u8>>,
description: Option<String>,
seed: Seed,

View File

@ -41,7 +41,7 @@ use log::warn;
use protobuf::Message;
use nix::{NixPath, fcntl::{FlockArg, OFlag}, sys::stat::Mode};
use nix::sys::statvfs::Statvfs;
use std::ffi::{CStr, CString};
use std::ffi::CStr;
use std::fs;
use std::io::{Read, Write};
use std::os::unix::io::{AsRawFd, RawFd};
@ -104,16 +104,14 @@ impl Drop for Fd {
impl Fd {
/// Opens the given path as a directory.
pub fn open(path: &str, mkdir: bool) -> Result<Fd, nix::Error> {
let cstring = CString::new(path).map_err(|_| nix::Error::InvalidPath)?;
pub fn open<P: ?Sized + NixPath>(path: &P, mkdir: bool) -> Result<Fd, nix::Error> {
if mkdir {
match nix::unistd::mkdir(cstring.as_c_str(), nix::sys::stat::Mode::S_IRWXU) {
match nix::unistd::mkdir(path, nix::sys::stat::Mode::S_IRWXU) {
Ok(()) | Err(nix::Error::Sys(nix::errno::Errno::EEXIST)) => {},
Err(e) => return Err(e),
}
}
let fd = nix::fcntl::open(cstring.as_c_str(), OFlag::O_DIRECTORY | OFlag::O_RDONLY,
Mode::empty())?;
let fd = nix::fcntl::open(path, OFlag::O_DIRECTORY | OFlag::O_RDONLY, Mode::empty())?;
Ok(Fd(fd))
}

View File

@ -52,9 +52,9 @@ const UPGRADE_NOTES: &'static str =
#[derive(Debug)]
pub struct Args<'a> {
pub flag_sample_file_dir: Option<&'a str>,
pub flag_preset_journal: &'a str,
pub flag_no_vacuum: bool,
pub sample_file_dir: Option<&'a std::path::Path>,
pub preset_journal: &'a str,
pub no_vacuum: bool,
}
fn set_journal_mode(conn: &rusqlite::Connection, requested: &str) -> Result<(), Error> {
@ -86,7 +86,7 @@ fn upgrade(args: &Args, target_ver: i32, conn: &mut rusqlite::Connection) -> Res
bail!("Database is at negative version {}!", old_ver);
}
info!("Upgrading database from version {} to version {}...", old_ver, target_ver);
set_journal_mode(&conn, args.flag_preset_journal)?;
set_journal_mode(&conn, args.preset_journal)?;
for ver in old_ver .. target_ver {
info!("...from version {} to version {}", ver, ver + 1);
let tx = conn.transaction()?;
@ -118,7 +118,7 @@ pub fn run(args: &Args, conn: &mut rusqlite::Connection) -> Result<(), Error> {
// WAL is the preferred journal mode for normal operation; it reduces the number of syncs
// without compromising safety.
set_journal_mode(&conn, "wal")?;
if !args.flag_no_vacuum {
if !args.no_vacuum {
info!("...vacuuming database after upgrade.");
conn.execute_batch(r#"
pragma page_size = 16384;
@ -157,7 +157,7 @@ impl NixPath for UuidPath {
mod tests {
use crate::compare;
use crate::testutil;
use failure::{ResultExt, format_err};
use failure::ResultExt;
use super::*;
fn new_conn() -> Result<rusqlite::Connection, Error> {
@ -183,7 +183,7 @@ mod tests {
fn upgrade_and_compare() -> Result<(), Error> {
testutil::init();
let tmpdir = tempdir::TempDir::new("moonfire-nvr-test")?;
let path = tmpdir.path().to_str().ok_or_else(|| format_err!("invalid UTF-8"))?.to_owned();
//let path = tmpdir.path().to_str().ok_or_else(|| format_err!("invalid UTF-8"))?.to_owned();
let mut upgraded = new_conn()?;
upgraded.execute_batch(include_str!("v0.sql"))?;
upgraded.execute_batch(r#"
@ -218,9 +218,9 @@ mod tests {
(4, None), // transitional; don't compare schemas.
(5, Some(include_str!("../schema.sql")))] {
upgrade(&Args {
flag_sample_file_dir: Some(&path),
flag_preset_journal: "delete",
flag_no_vacuum: false,
sample_file_dir: Some(&tmpdir.path()),
preset_journal: "delete",
no_vacuum: false,
}, *ver, &mut upgraded).context(format!("upgrading to version {}", ver))?;
if let Some(f) = fresh_sql {
compare(&upgraded, *ver, f)?;

View File

@ -42,7 +42,7 @@ use uuid::Uuid;
pub fn run(args: &super::Args, tx: &rusqlite::Transaction) -> Result<(), Error> {
let sample_file_path =
args.flag_sample_file_dir
args.sample_file_dir
.ok_or_else(|| format_err!("--sample-file-dir required when upgrading from \
schema version 1 to 2."))?;
@ -122,6 +122,9 @@ pub fn run(args: &super::Args, tx: &rusqlite::Transaction) -> Result<(), Error>
}
dir::write_meta(d.as_raw_fd(), &meta)?;
let sample_file_path = sample_file_path.to_str()
.ok_or_else(|| format_err!("sample file dir {} is not a valid string",
sample_file_path.display()))?;
tx.execute(r#"
insert into sample_file_dir (path, uuid, last_complete_open_id)
values (?, ?, ?)
@ -293,7 +296,7 @@ pub fn run(args: &super::Args, tx: &rusqlite::Transaction) -> Result<(), Error>
/// * optional: reserved sample file uuids.
/// * optional: meta and meta-tmp from half-completed update attempts.
/// * forbidden: anything else.
fn verify_dir_contents(sample_file_path: &str, dir: &mut nix::dir::Dir,
fn verify_dir_contents(sample_file_path: &std::path::Path, dir: &mut nix::dir::Dir,
tx: &rusqlite::Transaction) -> Result<(), Error> {
// Build a hash of the uuids found in the directory.
let n: i64 = tx.query_row(r#"
@ -337,7 +340,7 @@ fn verify_dir_contents(sample_file_path: &str, dir: &mut nix::dir::Dir,
while let Some(row) = rows.next()? {
let uuid: crate::db::FromSqlUuid = row.get(0)?;
if !files.remove(&uuid.0) {
bail!("{} is missing from dir {}!", uuid.0, sample_file_path);
bail!("{} is missing from dir {}!", uuid.0, sample_file_path.display());
}
}
}
@ -360,7 +363,7 @@ fn verify_dir_contents(sample_file_path: &str, dir: &mut nix::dir::Dir,
if !files.is_empty() {
bail!("{} unexpected sample file uuids in dir {}: {:?}!",
files.len(), sample_file_path, files);
files.len(), sample_file_path.display(), files);
}
Ok(())
}

View File

@ -1,5 +1,5 @@
// This file is part of Moonfire NVR, a security camera network video recorder.
// Copyright (C) 2018 The Moonfire NVR Authors
// Copyright (C) 2018-2020 The Moonfire NVR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
@ -32,36 +32,25 @@
use db::check;
use failure::Error;
use serde::Deserialize;
use std::path::PathBuf;
use structopt::StructOpt;
static USAGE: &'static str = r#"
Checks database integrity.
#[derive(StructOpt)]
pub struct Args {
/// Directory holding the SQLite3 index database.
#[structopt(long, default_value = "/var/lib/moonfire-nvr/db", value_name="path",
parse(from_os_str))]
db_dir: PathBuf,
Usage:
moonfire-nvr check [options]
moonfire-nvr check --help
Options:
--db-dir=DIR Set the directory holding the SQLite3 index database.
This is typically on a flash device.
[default: /var/lib/moonfire-nvr/db]
--compare-lens Compare sample file lengths on disk to the database.
"#;
#[derive(Debug, Deserialize)]
struct Args {
flag_db_dir: String,
flag_compare_lens: bool,
/// Compare sample file lengths on disk to the database.
#[structopt(long)]
compare_lens: bool,
}
pub fn run() -> Result<(), Error> {
let args: Args = super::parse_args(USAGE)?;
pub fn run(args: &Args) -> Result<(), Error> {
// TODO: ReadOnly should be sufficient but seems to fail.
let (_db_dir, conn) = super::open_conn(&args.flag_db_dir, super::OpenMode::ReadWrite)?;
let (_db_dir, conn) = super::open_conn(&args.db_dir, super::OpenMode::ReadWrite)?;
check::run(&conn, &check::Options {
compare_lens: args.flag_compare_lens,
compare_lens: args.compare_lens,
})
}

View File

@ -38,36 +38,24 @@ use cursive::Cursive;
use cursive::views;
use db;
use failure::Error;
use serde::Deserialize;
use std::path::PathBuf;
use std::sync::Arc;
use structopt::StructOpt;
mod cameras;
mod dirs;
mod users;
static USAGE: &'static str = r#"
Interactive configuration editor.
Usage:
moonfire-nvr config [options]
moonfire-nvr config --help
Options:
--db-dir=DIR Set the directory holding the SQLite3 index database.
This is typically on a flash device.
[default: /var/lib/moonfire-nvr/db]
"#;
#[derive(Debug, Deserialize)]
struct Args {
flag_db_dir: String,
#[derive(StructOpt)]
pub struct Args {
/// Directory holding the SQLite3 index database.
#[structopt(long, default_value = "/var/lib/moonfire-nvr/db", value_name="path",
parse(from_os_str))]
db_dir: PathBuf,
}
pub fn run() -> Result<(), Error> {
let args: Args = super::parse_args(USAGE)?;
let (_db_dir, conn) = super::open_conn(&args.flag_db_dir, super::OpenMode::ReadWrite)?;
pub fn run(args: &Args) -> Result<(), Error> {
let (_db_dir, conn) = super::open_conn(&args.db_dir, super::OpenMode::ReadWrite)?;
let clocks = clock::RealClocks {};
let db = Arc::new(db::Database::new(clocks, conn, true)?);

View File

@ -1,5 +1,5 @@
// This file is part of Moonfire NVR, a security camera network video recorder.
// Copyright (C) 2016 The Moonfire NVR Authors
// Copyright (C) 2016-2020 The Moonfire NVR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
@ -30,31 +30,19 @@
use failure::Error;
use log::info;
use serde::Deserialize;
use structopt::StructOpt;
use std::path::PathBuf;
static USAGE: &'static str = r#"
Initializes a database.
Usage:
moonfire-nvr init [options]
moonfire-nvr init --help
Options:
--db-dir=DIR Set the directory holding the SQLite3 index database.
This is typically on a flash device.
[default: /var/lib/moonfire-nvr/db]
"#;
#[derive(Debug, Deserialize)]
struct Args {
flag_db_dir: String,
#[derive(StructOpt)]
pub struct Args {
/// Directory holding the SQLite3 index database.
#[structopt(long, default_value = "/var/lib/moonfire-nvr/db", value_name="path",
parse(from_os_str))]
db_dir: PathBuf,
}
pub fn run() -> Result<(), Error> {
let args: Args = super::parse_args(USAGE)?;
let (_db_dir, mut conn) = super::open_conn(&args.flag_db_dir, super::OpenMode::Create)?;
pub fn run(args: &Args) -> Result<(), Error> {
let (_db_dir, mut conn) = super::open_conn(&args.db_dir, super::OpenMode::Create)?;
// Check if the database has already been initialized.
let cur_ver = db::get_schema_version(&conn)?;

View File

@ -1,5 +1,5 @@
// This file is part of Moonfire NVR, a security camera network video recorder.
// Copyright (C) 2019 The Moonfire NVR Authors
// Copyright (C) 2019-2020 The Moonfire NVR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
@ -31,92 +31,74 @@
//! Subcommand to login a user (without requiring a password).
use base::clock::{self, Clocks};
use db::auth::SessionFlags;
use failure::{Error, ResultExt, bail, format_err};
use serde::Deserialize;
use db::auth::SessionFlag;
use failure::{Error, format_err};
use std::os::unix::fs::OpenOptionsExt as _;
use std::io::Write as _;
use std::path::PathBuf;
use structopt::StructOpt;
static USAGE: &'static str = r#"
Logs in a user, returning the session cookie.
#[derive(Debug, Default, StructOpt)]
pub struct Args {
/// Directory holding the SQLite3 index database.
#[structopt(long, default_value = "/var/lib/moonfire-nvr/db", value_name="path",
parse(from_os_str))]
db_dir: PathBuf,
This is a privileged command that directly accesses the database. It doesn't
check the user's password and even can be used to create sessions with
permissions the user doesn't have.
/// Create a session with the given permissions.
///
/// If unspecified, uses user's default permissions.
#[structopt(long, value_name="perms",
parse(try_from_str = protobuf::text_format::parse_from_str))]
permissions: Option<db::Permissions>,
Usage:
/// Restrict this cookie to the given domain.
#[structopt(long)]
domain: Option<String>,
moonfire-nvr login [options] <username>
moonfire-nvr login --help
/// Write the cookie to a new curl-compatible cookie-jar file.
///
/// ---domain must be specified. This file can be used later with curl's --cookie flag.
#[structopt(long, requires("domain"), value_name="path")]
curl_cookie_jar: Option<PathBuf>,
Options:
/// Set the given db::auth::SessionFlags.
#[structopt(long, default_value="http-only,secure,same-site,same-site-strict",
value_name="flags", use_delimiter=true)]
session_flags: Vec<SessionFlag>,
--db-dir=DIR Set the directory holding the SQLite3 index database. This
is typically on a flash device.
[default: /var/lib/moonfire-nvr/db]
--permissions=PERMISSIONS
Create a session with the given permissions. If
unspecified, uses user's default permissions.
--domain=DOMAIN The domain this cookie lives on. Optional.
--curl-cookie-jar=FILE
Writes the cookie to a new curl-compatible cookie-jar
file. --domain must be specified. This can be used later
with curl's --cookie flag.
--session-flags=FLAGS
Set the given db::auth::SessionFlags.
[default: http-only,secure,same-site,same-site-strict]
"#;
#[derive(Debug, Default, Deserialize, Eq, PartialEq)]
struct Args {
flag_db_dir: String,
flag_permissions: Option<String>,
flag_domain: Option<String>,
flag_curl_cookie_jar: Option<PathBuf>,
flag_session_flags: String,
arg_username: String,
/// Create the session for this username.
username: String,
}
pub fn run() -> Result<(), Error> {
let args: Args = super::parse_args(USAGE)?;
pub fn run(args: &Args) -> Result<(), Error> {
let clocks = clock::RealClocks {};
let (_db_dir, conn) = super::open_conn(&args.flag_db_dir, super::OpenMode::ReadWrite)?;
let (_db_dir, conn) = super::open_conn(&args.db_dir, super::OpenMode::ReadWrite)?;
let db = std::sync::Arc::new(db::Database::new(clocks.clone(), conn, true).unwrap());
let mut l = db.lock();
let u = l.get_user(&args.arg_username)
.ok_or_else(|| format_err!("no such user {:?}", &args.arg_username))?;
let permissions = match args.flag_permissions {
None => u.permissions.clone(),
Some(s) => protobuf::text_format::parse_from_str(&s)
.context("unable to parse --permissions")?
};
let u = l.get_user(&args.username)
.ok_or_else(|| format_err!("no such user {:?}", &args.username))?;
let permissions = args.permissions.as_ref().unwrap_or(&u.permissions).clone();
let creation = db::auth::Request {
when_sec: Some(db.clocks().realtime().sec),
user_agent: None,
addr: None,
};
let mut flags = 0;
for f in args.flag_session_flags.split(',') {
flags |= match f {
"http-only" => SessionFlags::HttpOnly,
"secure" => SessionFlags::Secure,
"same-site" => SessionFlags::SameSite,
"same-site-strict" => SessionFlags::SameSiteStrict,
_ => bail!("unknown session flag {:?}", f),
} as i32;
for f in &args.session_flags {
flags |= *f as i32;
}
let uid = u.id;
drop(u);
let (sid, _) = l.make_session(creation, uid,
args.flag_domain.as_ref().map(|d| d.as_bytes().to_owned()),
args.domain.as_ref().map(|d| d.as_bytes().to_owned()),
flags, permissions)?;
let mut encoded = [0u8; 64];
base64::encode_config_slice(&sid, base64::STANDARD_NO_PAD, &mut encoded);
let encoded = std::str::from_utf8(&encoded[..]).expect("base64 is valid UTF-8");
if let Some(ref p) = args.flag_curl_cookie_jar {
let d = args.flag_domain.as_ref()
if let Some(ref p) = args.curl_cookie_jar {
let d = args.domain.as_ref()
.ok_or_else(|| format_err!("--cookiejar requires --domain"))?;
let mut f = std::fs::OpenOptions::new()
.write(true)
@ -139,11 +121,11 @@ pub fn run() -> Result<(), Error> {
fn curl_cookie(cookie: &str, flags: i32, domain: &str) -> String {
format!("{httponly}{domain}\t{tailmatch}\t{path}\t{secure}\t{expires}\t{name}\t{value}",
httponly=if (flags & SessionFlags::HttpOnly as i32) != 0 { "#HttpOnly_" } else { "" },
httponly=if (flags & SessionFlag::HttpOnly as i32) != 0 { "#HttpOnly_" } else { "" },
domain=domain,
tailmatch="FALSE",
path="/",
secure=if (flags & SessionFlags::Secure as i32) != 0 { "TRUE" } else { "FALSE" },
secure=if (flags & SessionFlag::Secure as i32) != 0 { "TRUE" } else { "FALSE" },
expires="9223372036854775807", // 64-bit CURL_OFF_T_MAX, never expires
name="s",
value=cookie)
@ -153,24 +135,10 @@ fn curl_cookie(cookie: &str, flags: i32, domain: &str) -> String {
mod tests {
use super::*;
#[test]
fn test_args() {
let args: Args = docopt::Docopt::new(USAGE).unwrap()
.argv(&["nvr", "login", "--curl-cookie-jar=foo.txt", "slamb"])
.deserialize().unwrap();
assert_eq!(args, Args {
flag_db_dir: "/var/lib/moonfire-nvr/db".to_owned(),
flag_curl_cookie_jar: Some(PathBuf::from("foo.txt")),
flag_session_flags: "http-only,secure,same-site,same-site-strict".to_owned(),
arg_username: "slamb".to_owned(),
..Default::default()
});
}
#[test]
fn test_curl_cookie() {
assert_eq!(curl_cookie("o3mx3OntO7GzwwsD54OuyQ4IuipYrwPR2aiULPHSudAa+xIhwWjb+w1TnGRh8Z5Q",
SessionFlags::HttpOnly as i32, "localhost"),
SessionFlag::HttpOnly as i32, "localhost"),
"#HttpOnly_localhost\tFALSE\t/\tFALSE\t9223372036854775807\ts\t\
o3mx3OntO7GzwwsD54OuyQ4IuipYrwPR2aiULPHSudAa+xIhwWjb+w1TnGRh8Z5Q");
}

View File

@ -29,48 +29,19 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
use db::dir;
use docopt;
use failure::{Error, Fail};
use nix::fcntl::FlockArg;
use rusqlite;
use serde::Deserialize;
use std::path::Path;
mod check;
mod config;
mod login;
mod init;
mod run;
mod sql;
mod ts;
mod upgrade;
#[derive(Debug, Deserialize)]
pub enum Command {
Check,
Config,
Login,
Init,
Run,
Sql,
Ts,
Upgrade,
}
impl Command {
pub fn run(&self) -> Result<(), Error> {
match *self {
Command::Check => check::run(),
Command::Config => config::run(),
Command::Login => login::run(),
Command::Init => init::run(),
Command::Run => run::run(),
Command::Sql => sql::run(),
Command::Ts => ts::run(),
Command::Upgrade => upgrade::run(),
}
}
}
pub mod check;
pub mod config;
pub mod login;
pub mod init;
pub mod run;
pub mod sql;
pub mod ts;
pub mod upgrade;
#[derive(Copy, Clone, PartialEq, Eq)]
enum OpenMode {
@ -81,10 +52,10 @@ enum OpenMode {
/// Locks the directory without opening the database.
/// The returned `dir::Fd` holds the lock and should be kept open as long as the `Connection` is.
fn open_dir(db_dir: &str, mode: OpenMode) -> Result<dir::Fd, Error> {
fn open_dir(db_dir: &Path, mode: OpenMode) -> Result<dir::Fd, Error> {
let dir = dir::Fd::open(db_dir, mode == OpenMode::Create)?;
let ro = mode == OpenMode::ReadOnly;
dir.lock(if ro { FlockArg::LockExclusiveNonblock } else { FlockArg::LockSharedNonblock })
dir.lock(if ro { FlockArg::LockSharedNonblock } else { FlockArg::LockExclusiveNonblock })
.map_err(|e| e.context(format!("db dir {:?} already in use; can't get {} lock",
db_dir, if ro { "shared" } else { "exclusive" })))?;
Ok(dir)
@ -92,10 +63,10 @@ fn open_dir(db_dir: &str, mode: OpenMode) -> Result<dir::Fd, Error> {
/// Locks and opens the database.
/// The returned `dir::Fd` holds the lock and should be kept open as long as the `Connection` is.
fn open_conn(db_dir: &str, mode: OpenMode) -> Result<(dir::Fd, rusqlite::Connection), Error> {
fn open_conn(db_dir: &Path, mode: OpenMode) -> Result<(dir::Fd, rusqlite::Connection), Error> {
let dir = open_dir(db_dir, mode)?;
let conn = rusqlite::Connection::open_with_flags(
Path::new(&db_dir).join("db"),
db_dir.join("db"),
match mode {
OpenMode::ReadOnly => rusqlite::OpenFlags::SQLITE_OPEN_READ_ONLY,
OpenMode::ReadWrite => rusqlite::OpenFlags::SQLITE_OPEN_READ_WRITE,
@ -108,9 +79,3 @@ fn open_conn(db_dir: &str, mode: OpenMode) -> Result<(dir::Fd, rusqlite::Connect
rusqlite::OpenFlags::SQLITE_OPEN_NO_MUTEX)?;
Ok((dir, conn))
}
fn parse_args<'a, T>(usage: &str) -> Result<T, Error> where T: ::serde::Deserialize<'a> {
Ok(docopt::Docopt::new(usage)
.and_then(|d| d.deserialize())
.unwrap_or_else(|e| e.exit()))
}

View File

@ -1,5 +1,5 @@
// This file is part of Moonfire NVR, a security camera network video recorder.
// Copyright (C) 2016 The Moonfire NVR Authors
// Copyright (C) 2016-2020 The Moonfire NVR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
@ -33,19 +33,60 @@ use crate::stream;
use crate::streamer;
use crate::web;
use db::{dir, writer};
use failure::{Error, ResultExt, bail};
use failure::{Error, bail};
use fnv::FnvHashMap;
use futures::future::FutureExt;
use hyper::service::{make_service_fn, service_fn};
use log::{info, warn};
use serde::Deserialize;
use std::path::PathBuf;
use std::pin::Pin;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::thread;
use structopt::StructOpt;
use tokio;
use tokio::signal::unix::{SignalKind, signal};
#[derive(StructOpt)]
pub struct Args {
/// Directory holding the SQLite3 index database.
#[structopt(long, default_value = "/var/lib/moonfire-nvr/db", value_name="path",
parse(from_os_str))]
db_dir: PathBuf,
/// Directory holding user interface files (.html, .js, etc).
#[structopt(default_value = "/usr/local/lib/moonfire-nvr/ui", value_name="path",
parse(from_os_str))]
ui_dir: std::path::PathBuf,
/// Bind address for unencrypted HTTP server.
#[structopt(long, default_value = "0.0.0.0:8080", parse(try_from_str))]
http_addr: std::net::SocketAddr,
/// Open the database in read-only mode and disables recording.
///
/// Note this is incompatible with authentication, so you'll likely want to specify
/// --allow_unauthenticated_permissions.
#[structopt(long)]
read_only: bool,
/// Allow unauthenticated access to the web interface, with the given permissions (may be
/// empty). Should be a text Permissions protobuf such as "view_videos: true".
///
/// Note that even an empty string allows some basic access that would be rejected if the
/// argument were omitted.
#[structopt(long, parse(try_from_str = protobuf::text_format::parse_from_str))]
allow_unauthenticated_permissions: Option<db::Permissions>,
/// Trust X-Real-IP: and X-Forwarded-Proto: headers on the incoming request.
///
/// Set this only after ensuring your proxy server is configured to set them and that no
/// untrusted requests bypass the proxy server. You may want to specify
/// --http-addr=127.0.0.1:8080.
#[structopt(long)]
trust_forward_hdrs: bool,
}
// These are used in a hack to get the name of the current time zone (e.g. America/Los_Angeles).
// They seem to be correct for Linux and macOS at least.
const LOCALTIME_PATH: &'static str = "/etc/localtime";
@ -55,44 +96,6 @@ const ZONEINFO_PATHS: [&'static str; 2] = [
"/var/db/timezone/zoneinfo/" // macOS High Sierra
];
const USAGE: &'static str = r#"
Usage: moonfire-nvr run [options]
Options:
-h, --help Show this message.
--db-dir=DIR Set the directory holding the SQLite3 index database.
This is typically on a flash device.
[default: /var/lib/moonfire-nvr/db]
--ui-dir=DIR Set the directory with the user interface files
(.html, .js, etc).
[default: /usr/local/lib/moonfire-nvr/ui]
--http-addr=ADDR Set the bind address for the unencrypted HTTP server.
[default: 0.0.0.0:8080]
--read-only Forces read-only mode / disables recording.
--allow-unauthenticated-permissions=PERMISSIONS
Allow unauthenticated access to the web interface,
with the given permissions (may be empty).
PERMISSIONS should be a text Permissions protobuf
such as "view_videos: true". NOTE: even an empty
string allows some basic access that would be
rejected if the argument were omitted.
--trust-forward-hdrs Trust X-Real-IP: and X-Forwarded-Proto: headers on
the incoming request. Set this only after ensuring
your proxy server is configured to set them and that
no untrusted requests bypass the proxy server.
You may want to specify --http-addr=127.0.0.1:8080.
"#;
#[derive(Debug, Deserialize)]
struct Args {
flag_db_dir: String,
flag_http_addr: String,
flag_ui_dir: String,
flag_read_only: bool,
flag_allow_unauthenticated_permissions: Option<String>,
flag_trust_forward_hdrs: bool,
}
fn trim_zoneinfo(p: &str) -> &str {
for zp in &ZONEINFO_PATHS {
if p.starts_with(zp) {
@ -167,13 +170,12 @@ struct Syncer {
}
#[tokio::main]
pub async fn run() -> Result<(), Error> {
let args: Args = super::parse_args(USAGE)?;
pub async fn run(args: &Args) -> Result<(), Error> {
let clocks = clock::RealClocks {};
let (_db_dir, conn) = super::open_conn(
&args.flag_db_dir,
if args.flag_read_only { super::OpenMode::ReadOnly } else { super::OpenMode::ReadWrite })?;
let db = Arc::new(db::Database::new(clocks.clone(), conn, !args.flag_read_only).unwrap());
&args.db_dir,
if args.read_only { super::OpenMode::ReadOnly } else { super::OpenMode::ReadWrite })?;
let db = Arc::new(db::Database::new(clocks.clone(), conn, !args.read_only).unwrap());
info!("Database is loaded.");
{
@ -186,22 +188,18 @@ pub async fn run() -> Result<(), Error> {
let time_zone_name = resolve_zone()?;
info!("Resolved timezone: {}", &time_zone_name);
let allow_unauthenticated_permissions = args.flag_allow_unauthenticated_permissions
.map(|s| protobuf::text_format::parse_from_str(&s))
.transpose()
.context("Unable to parse --allow-unauthenticated-permissions")?;
let s = web::Service::new(web::Config {
db: db.clone(),
ui_dir: Some(&args.flag_ui_dir),
allow_unauthenticated_permissions,
trust_forward_hdrs: args.flag_trust_forward_hdrs,
ui_dir: Some(&args.ui_dir),
allow_unauthenticated_permissions: args.allow_unauthenticated_permissions.clone(),
trust_forward_hdrs: args.trust_forward_hdrs,
time_zone_name,
})?;
// Start a streamer for each stream.
let shutdown_streamers = Arc::new(AtomicBool::new(false));
let mut streamers = Vec::new();
let syncers = if !args.flag_read_only {
let syncers = if !args.read_only {
let l = db.lock();
let mut dirs = FnvHashMap::with_capacity_and_hasher(
l.sample_file_dirs_by_id().len(), Default::default());
@ -267,14 +265,13 @@ pub async fn run() -> Result<(), Error> {
} else { None };
// Start the web interface.
let addr = args.flag_http_addr.parse().unwrap();
let make_svc = make_service_fn(move |_conn| {
futures::future::ok::<_, std::convert::Infallible>(service_fn({
let mut s = s.clone();
move |req| Pin::from(s.serve(req))
}))
});
let server = ::hyper::server::Server::bind(&addr)
let server = ::hyper::server::Server::bind(&args.http_addr)
.tcp_nodelay(true)
.serve(make_svc);

View File

@ -1,5 +1,5 @@
// This file is part of Moonfire NVR, a security camera network video recorder.
// Copyright (C) 2019 The Moonfire NVR Authors
// Copyright (C) 2019-2020 The Moonfire NVR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
@ -31,45 +31,43 @@
//! Subcommand to run a SQLite shell.
use failure::Error;
use serde::Deserialize;
use std::ffi::OsString;
use std::path::PathBuf;
use std::process::Command;
use super::OpenMode;
use structopt::StructOpt;
static USAGE: &'static str = r#"
Runs a SQLite shell on the Moonfire NVR database with locking.
#[derive(StructOpt)]
pub struct Args {
/// Directory holding the SQLite3 index database.
#[structopt(long, default_value = "/var/lib/moonfire-nvr/db", value_name="path",
parse(from_os_str))]
db_dir: PathBuf,
Usage:
/// Opens the database in read-only mode and locks it only for shared access.
///
/// This can be run simultaneously with "moonfire-nvr run --read-only".
#[structopt(long)]
read_only: bool,
moonfire-nvr sql [options] [--] [<arg>...]
moonfire-nvr sql --help
Positional arguments will be passed to sqlite3. Use the -- separator to pass
sqlite3 options, as in "moonfire-nvr sql -- -line 'select username from user'".
Options:
--db-dir=DIR Set the directory holding the SQLite3 index database.
This is typically on a flash device.
[default: /var/lib/moonfire-nvr/db]
--read-only Accesses the database in read-only mode.
"#;
#[derive(Debug, Deserialize)]
struct Args {
flag_db_dir: String,
flag_read_only: bool,
arg_arg: Vec<String>,
/// Arguments to pass to sqlite3.
///
/// Use the -- separator to pass sqlite3 options, as in
/// "moonfire-nvr sql -- -line 'select username from user'".
#[structopt(parse(from_os_str))]
arg: Vec<OsString>,
}
pub fn run() -> Result<(), Error> {
let args: Args = super::parse_args(USAGE)?;
let mode = if args.flag_read_only { OpenMode::ReadWrite } else { OpenMode::ReadOnly };
let _db_dir = super::open_dir(&args.flag_db_dir, mode)?;
let mut db = format!("file:{}/db", &args.flag_db_dir);
if args.flag_read_only {
db.push_str("?mode=ro");
pub fn run(args: &Args) -> Result<(), Error> {
let mode = if args.read_only { OpenMode::ReadOnly } else { OpenMode::ReadWrite };
let _db_dir = super::open_dir(&args.db_dir, mode)?;
let mut db = OsString::new();
db.push("file:");
db.push(&args.db_dir);
db.push("/db");
if args.read_only {
db.push("?mode=ro");
}
Command::new("sqlite3").arg(&db).args(&args.arg_arg).status()?;
Command::new("sqlite3").arg(&db).args(&args.arg).status()?;
Ok(())
}

View File

@ -1,5 +1,5 @@
// This file is part of Moonfire NVR, a security camera network video recorder.
// Copyright (C) 2016 The Moonfire NVR Authors
// Copyright (C) 2016-2020 The Moonfire NVR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
@ -29,21 +29,22 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
use failure::Error;
use serde::Deserialize;
use structopt::StructOpt;
const USAGE: &'static str = r#"
Usage: moonfire-nvr ts <ts>...
moonfire-nvr ts --help
"#;
#[derive(Debug, Deserialize)]
struct Args {
arg_ts: Vec<String>,
#[derive(StructOpt)]
pub struct Args {
/// Timestamp(s) to translate.
///
/// May be either an integer or an RFC-3339-like string:
/// YYYY-mm-dd[THH:MM[:SS[:FFFFF]]][{Z,{+,-,}HH:MM}].
///
/// Eg: 142913484000000, 2020-04-26, 2020-04-26T12:00:00:00000-07:00.
#[structopt(required = true)]
timestamps: Vec<String>,
}
pub fn run() -> Result<(), Error> {
let arg: Args = super::parse_args(&USAGE)?;
for timestamp in &arg.arg_ts {
pub fn run(args: &Args) -> Result<(), Error> {
for timestamp in &args.timestamps {
let t = db::recording::Time::parse(timestamp)?;
println!("{} == {}", t, t.0);
}

View File

@ -1,5 +1,5 @@
// This file is part of Moonfire NVR, a security camera network video recorder.
// Copyright (C) 2016 The Moonfire NVR Authors
// Copyright (C) 2016-2020 The Moonfire NVR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
@ -33,45 +33,38 @@
/// See `guide/schema.md` for more information.
use failure::Error;
use serde::Deserialize;
use structopt::StructOpt;
const USAGE: &'static str = r#"
Upgrade to the latest database schema.
Usage: moonfire-nvr upgrade [options]
Options:
-h, --help Show this message.
--db-dir=DIR Set the directory holding the SQLite3 index database.
This is typically on a flash device.
[default: /var/lib/moonfire-nvr/db]
--sample-file-dir=DIR When upgrading from schema version 1 to 2, the sample file directory.
This is typically on a hard drive.
--preset-journal=MODE Resets the SQLite journal_mode to the specified mode
prior to the upgrade. The default, delete, is
recommended. off is very dangerous but may be
desirable in some circumstances. See guide/schema.md
for more information. The journal mode will be reset
to wal after the upgrade.
[default: delete]
--no-vacuum Skips the normal post-upgrade vacuum operation.
"#;
#[derive(Debug, Deserialize)]
#[derive(StructOpt)]
pub struct Args {
flag_db_dir: String,
flag_sample_file_dir: Option<String>,
flag_preset_journal: String,
flag_no_vacuum: bool,
#[structopt(long,
help = "Directory holding the SQLite3 index database.",
default_value = "/var/lib/moonfire-nvr/db",
parse(from_os_str))]
db_dir: std::path::PathBuf,
#[structopt(help = "When upgrading from schema version 1 to 2, the sample file directory.",
long, parse(from_os_str))]
sample_file_dir: Option<std::path::PathBuf>,
#[structopt(help = "Resets the SQLite journal_mode to the specified mode prior to the \
upgrade. The default, delete, is recommended. off is very dangerous \
but may be desirable in some circumstances. See guide/schema.md for \
more information. The journal mode will be reset to wal after the \
upgrade.",
long, default_value = "delete")]
preset_journal: String,
#[structopt(help = "Skips the normal post-upgrade vacuum operation.", long)]
no_vacuum: bool,
}
pub fn run() -> Result<(), Error> {
let args: Args = super::parse_args(USAGE)?;
let (_db_dir, mut conn) = super::open_conn(&args.flag_db_dir, super::OpenMode::ReadWrite)?;
pub fn run(args: &Args) -> Result<(), Error> {
let (_db_dir, mut conn) = super::open_conn(&args.db_dir, super::OpenMode::ReadWrite)?;
db::upgrade::run(&db::upgrade::Args {
flag_sample_file_dir: args.flag_sample_file_dir.as_ref().map(|s| s.as_str()),
flag_preset_journal: &args.flag_preset_journal,
flag_no_vacuum: args.flag_no_vacuum,
sample_file_dir: args.sample_file_dir.as_ref().map(std::path::PathBuf::as_path),
preset_journal: &args.preset_journal,
no_vacuum: args.no_vacuum,
}, &mut conn)
}

View File

@ -1,5 +1,5 @@
// This file is part of Moonfire NVR, a security camera network video recorder.
// Copyright (C) 2016 The Moonfire NVR Authors
// Copyright (C) 2016-2020 The Moonfire NVR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
@ -31,7 +31,7 @@
#![cfg_attr(all(feature="nightly", test), feature(test))]
use log::{error, info};
use serde::Deserialize;
use structopt::StructOpt;
mod body;
mod cmds;
@ -43,39 +43,53 @@ mod stream;
mod streamer;
mod web;
/// Commandline usage string. This is in the particular format expected by the `docopt` crate.
/// Besides being printed on --help or argument parsing error, it's actually parsed to define the
/// allowed commandline arguments and their defaults.
const USAGE: &'static str = "
Usage: moonfire-nvr <command> [<args>...]
moonfire-nvr (--help | --version)
#[derive(StructOpt)]
#[structopt(name="moonfire-nvr", about="security camera network video recorder")]
enum Args {
/// Checks database integrity (like fsck).
Check(cmds::check::Args),
Options:
-h, --help Show this message.
--version Show the version of moonfire-nvr.
/// Interactively edits configuration.
Config(cmds::config::Args),
Commands:
check Check database integrity
init Initialize a database
run Run the daemon: record from cameras and serve HTTP
shell Start an interactive shell to modify the database
ts Translate human-readable and numeric timestamps
upgrade Upgrade the database to the latest schema
";
/// Initializes a database.
Init(cmds::init::Args),
/// Commandline arguments corresponding to `USAGE`; automatically filled by the `docopt` crate.
#[derive(Debug, Deserialize)]
struct Args {
arg_command: Option<cmds::Command>,
/// Logs in a user, returning the session cookie.
///
/// This is a privileged command that directly accesses the database. It doesn't check the
/// user's password and even can be used to create sessions with permissions the user doesn't
/// have.
Login(cmds::login::Args),
/// Runs the server, saving recordings and allowing web access.
Run(cmds::run::Args),
/// Runs a SQLite3 shell on Moonfire NVR's index database.
///
/// Note this locks the database to prevent simultaneous access with a running server. The
/// server maintains cached state which could be invalidated otherwise.
Sql(cmds::sql::Args),
/// Translates between integer and human-readable timestamps.
Ts(cmds::ts::Args),
/// Upgrades to the latest database schema.
Upgrade(cmds::upgrade::Args),
}
fn version() -> String {
let major = option_env!("CARGO_PKG_VERSION_MAJOR");
let minor = option_env!("CARGO_PKG_VERSION_MAJOR");
let patch = option_env!("CARGO_PKG_VERSION_MAJOR");
match (major, minor, patch) {
(Some(major), Some(minor), Some(patch)) => format!("{}.{}.{}", major, minor, patch),
_ => "".to_owned(),
impl Args {
fn run(&self) -> Result<(), failure::Error> {
match self {
Args::Check(ref a) => cmds::check::run(a),
Args::Config(ref a) => cmds::config::run(a),
Args::Init(ref a) => cmds::init::run(a),
Args::Login(ref a) => cmds::login::run(a),
Args::Run(ref a) => cmds::run::run(a),
Args::Sql(ref a) => cmds::sql::run(a),
Args::Ts(ref a) => cmds::ts::run(a),
Args::Upgrade(ref a) => cmds::upgrade::run(a),
}
}
}
@ -88,14 +102,7 @@ fn parse_fmt<S: AsRef<str>>(fmt: S) -> Option<mylog::Format> {
}
fn main() {
// Parse commandline arguments.
// (Note this differs from cmds::parse_args in that it specifies options_first.)
let args: Args = docopt::Docopt::new(USAGE)
.and_then(|d| d.options_first(true)
.version(Some(version()))
.deserialize())
.unwrap_or_else(|e| e.exit());
let args = Args::from_args();
let mut h = mylog::Builder::new()
.set_format(::std::env::var("MOONFIRE_FORMAT")
.ok()
@ -105,7 +112,7 @@ fn main() {
.build();
h.clone().install().unwrap();
if let Err(e) = { let _a = h.r#async(); args.arg_command.unwrap().run() } {
if let Err(e) = { let _a = h.r#async(); args.run() } {
error!("{:?}", e);
::std::process::exit(1);
}

View File

@ -588,10 +588,10 @@ impl ServiceInner {
}.to_owned();
let mut l = self.db.lock();
let is_secure = self.is_secure(req);
let flags = (auth::SessionFlags::HttpOnly as i32) |
(auth::SessionFlags::SameSite as i32) |
(auth::SessionFlags::SameSiteStrict as i32) |
if is_secure { auth::SessionFlags::Secure as i32 } else { 0 };
let flags = (auth::SessionFlag::HttpOnly as i32) |
(auth::SessionFlag::SameSite as i32) |
(auth::SessionFlag::SameSiteStrict as i32) |
if is_secure { auth::SessionFlag::Secure as i32 } else { 0 };
let (sid, _) = l.login_by_password(authreq, &r.username, r.password, Some(domain),
flags)
.map_err(|e| plain_response(StatusCode::UNAUTHORIZED, e.to_string()))?;
@ -797,7 +797,7 @@ async fn with_json_body(mut req: Request<hyper::Body>)
pub struct Config<'a> {
pub db: Arc<db::Database>,
pub ui_dir: Option<&'a str>,
pub ui_dir: Option<&'a std::path::Path>,
pub trust_forward_hdrs: bool,
pub time_zone_name: String,
pub allow_unauthenticated_permissions: Option<db::Permissions>,
@ -840,12 +840,12 @@ impl Service {
})))
}
fn fill_ui_files(dir: &str, files: &mut HashMap<String, UiFile>) {
fn fill_ui_files(dir: &std::path::Path, files: &mut HashMap<String, UiFile>) {
let r = match fs::read_dir(dir) {
Ok(r) => r,
Err(e) => {
warn!("Unable to search --ui-dir={}; will serve no static files. Error was: {}",
dir, e);
dir.display(), e);
return;
}
};