aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorDaniel García <[email protected]>2019-02-05 22:17:02 +0100
committerDaniel García <[email protected]>2019-02-06 17:34:31 +0100
commit9976e4736e8fc43c91c7b6b8fa618153544154c8 (patch)
treee329c775c9e6a7a017518021aff2832ad92cbed3 /src
parentdc92f072324411c0f68b62ea80d358b9e5edafaf (diff)
downloadvaultwarden-9976e4736e8fc43c91c7b6b8fa618153544154c8.tar.gz
vaultwarden-9976e4736e8fc43c91c7b6b8fa618153544154c8.zip
Add groups
Diffstat (limited to 'src')
-rw-r--r--src/config.rs264
-rw-r--r--src/static/templates/admin/base.hbs5
-rw-r--r--src/static/templates/admin/page.hbs50
3 files changed, 177 insertions, 142 deletions
diff --git a/src/config.rs b/src/config.rs
index 77e2d441..616e96c8 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -13,8 +13,17 @@ lazy_static! {
}
macro_rules! make_config {
- ( $( $(#[doc = $doc:literal])+ $name:ident : $ty:ty, $editable:literal, $none_action:ident $(, $default:expr)? );+ $(;)? ) => {
-
+ (
+ $(
+ $(#[doc = $groupdoc:literal])?
+ $group:ident {
+ $(
+ $(#[doc = $doc:literal])+
+ $name:ident : $ty:ty, $editable:literal, $none_action:ident $(, $default:expr)?;
+ )+
+ },)+
+
+ ) => {
pub struct Config { inner: RwLock<Inner> }
struct Inner {
@@ -27,10 +36,10 @@ macro_rules! make_config {
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
pub struct ConfigBuilder {
- $(
+ $($(
#[serde(skip_serializing_if = "Option::is_none")]
- $name: Option<$ty>
- ),+
+ $name: Option<$ty>,
+ )+)+
}
impl ConfigBuilder {
@@ -38,9 +47,9 @@ macro_rules! make_config {
dotenv::dotenv().ok();
let mut builder = ConfigBuilder::default();
- $(
+ $($(
builder.$name = get_env(&stringify!($name).to_uppercase());
- )+
+ )+)+
builder
}
@@ -55,11 +64,11 @@ macro_rules! make_config {
/// If both have the same element, `other` wins.
fn merge(&self, other: &Self) -> Self {
let mut builder = self.clone();
- $(
+ $($(
if let v @Some(_) = &other.$name {
builder.$name = v.clone();
}
- )+
+ )+)+
builder
}
@@ -67,21 +76,21 @@ macro_rules! make_config {
/// except those that are equal in both sides
fn remove(&self, other: &Self) -> Self {
let mut builder = ConfigBuilder::default();
- $(
+ $($(
if &self.$name != &other.$name {
builder.$name = self.$name.clone();
}
- )+
+ )+)+
builder
}
fn build(&self) -> ConfigItems {
let mut config = ConfigItems::default();
let _domain_set = self.domain.is_some();
- $(
+ $($(
config.$name = make_config!{ @build self.$name.clone(), &config, $none_action, $($default)? };
- )+
+ )+)+
config.domain_set = _domain_set;
config
@@ -89,15 +98,15 @@ macro_rules! make_config {
}
#[derive(Debug, Clone, Default)]
- pub struct ConfigItems { $(pub $name: make_config!{@type $ty, $none_action} ),+ }
+ pub struct ConfigItems { $($(pub $name: make_config!{@type $ty, $none_action}, )+)+ }
#[allow(unused)]
impl Config {
- $(
+ $($(
pub fn $name(&self) -> make_config!{@type $ty, $none_action} {
self.inner.read().unwrap().config.$name.clone()
}
- )+
+ )+)+
pub fn load() -> Result<Self, Error> {
// Loading from env and file
@@ -122,9 +131,9 @@ macro_rules! make_config {
}
pub fn prepare_json(&self) -> serde_json::Value {
- let cfg = {
+ let (def, cfg) = {
let inner = &self.inner.read().unwrap();
- inner._env.merge(&inner._usr)
+ (inner._env.build(), inner.config.clone())
};
@@ -139,24 +148,32 @@ macro_rules! make_config {
fn _get_doc(doc: &str) -> serde_json::Value {
let mut split = doc.split("|>").map(str::trim);
json!({
- "group": split.next(),
"name": split.next(),
"description": split.next()
})
}
- json!([ $( {
- "editable": $editable,
- "name": stringify!($name),
- "value": cfg.$name,
- "default": make_config!{ @default &cfg, $none_action, $($default)? },
- "type": _get_form_type(stringify!($ty)),
- "doc": _get_doc(concat!($($doc),+)),
- }, )+ ])
+ json!([ $({
+ "group": stringify!($group),
+ "groupdoc": make_config!{ @show $($groupdoc)? },
+ "elements": [
+ $( {
+ "editable": $editable,
+ "name": stringify!($name),
+ "value": cfg.$name,
+ "default": def.$name,
+ "type": _get_form_type(stringify!($ty)),
+ "doc": _get_doc(concat!($($doc),+)),
+ }, )+
+ ]}, )+ ])
}
}
};
+ // Group or empty string
+ ( @show ) => { "" };
+ ( @show $groupdoc:literal ) => { $groupdoc };
+
// Wrap the optionals in an Option type
( @type $ty:ty, option) => { Option<$ty> };
( @type $ty:ty, $id:ident) => { $ty };
@@ -173,108 +190,115 @@ macro_rules! make_config {
}
}
}};
-
- // Get a default value
- ( @default $config:expr, option, ) => { serde_json::Value::Null };
- ( @default $config:expr, def, $default:expr ) => { $default };
- ( @default $config:expr, auto, $default_fn:expr ) => {{
- let f: &Fn(ConfigItems) -> _ = &$default_fn;
- f($config.build())
- }};
-
}
//STRUCTURE:
-// /// Group |> Friendly Name |> Description (Optional)
-// name: type, is_editable, none_action, <default_value (Optional)>
+// /// Short description (without this they won't appear on the list)
+// group {
+// /// Friendly Name |> Description (Optional)
+// name: type, is_editable, none_action, <default_value (Optional)>
+// }
//
// Where none_action applied when the value wasn't provided and can be:
// def: Use a default value
// auto: Value is auto generated based on other values
// option: Value is optional
make_config! {
- /// folders |> Data folder |> Main data folder
- data_folder: String, false, def, "data".to_string();
-
- /// folders |> Database URL
- database_url: String, false, auto, |c| format!("{}/{}", c.data_folder, "db.sqlite3");
- /// folders |> Icon chache folder
- icon_cache_folder: String, false, auto, |c| format!("{}/{}", c.data_folder, "icon_cache");
- /// folders |> Attachments folder
- attachments_folder: String, false, auto, |c| format!("{}/{}", c.data_folder, "attachments");
- /// folders |> Templates folder
- templates_folder: String, false, auto, |c| format!("{}/{}", c.data_folder, "templates");
- /// folders |> Session JWT key
- rsa_key_filename: String, false, auto, |c| format!("{}/{}", c.data_folder, "rsa_key");
-
- /// ws |> Enable websocket notifications
- websocket_enabled: bool, false, def, false;
- /// ws |> Websocket address
- websocket_address: String, false, def, "0.0.0.0".to_string();
- /// ws |> Websocket port
- websocket_port: u16, false, def, 3012;
-
- /// folders |> Web vault folder
- web_vault_folder: String, false, def, "web-vault/".to_string();
- /// settings |> Enable web vault
- web_vault_enabled: bool, false, def, true;
-
- /// icons |> Positive icon cache expiry |> Number of seconds to consider that an already cached icon is fresh. After this period, the icon will be redownloaded
- icon_cache_ttl: u64, true, def, 2_592_000;
- /// icons |> Negative icon cache expiry |> Number of seconds before trying to download an icon that failed again.
- icon_cache_negttl: u64, true, def, 259_200;
-
- /// settings |> Disable icon downloads |> Set to true to disable icon downloading, this would still serve icons from $ICON_CACHE_FOLDER,
- /// but it won't produce any external network request. Needs to set $ICON_CACHE_TTL to 0,
- /// otherwise it will delete them and they won't be downloaded again.
- disable_icon_download: bool, true, def, false;
- /// settings |> Allow new signups |> Controls if new users can register. Note that while this is disabled, users could still be invited
- signups_allowed: bool, true, def, true;
- /// settings |> Allow invitations |> Controls whether users can be invited by organization admins, even when signups are disabled
- invitations_allowed: bool, true, def, true;
- /// settings |> Password iterations |> Number of server-side passwords hashing iterations. The changes only apply when a user changes their password. Not recommended to lower the value
- password_iterations: i32, true, def, 100_000;
- /// settings |> Show password hints |> Controls if the password hint should be shown directly in the web page. Otherwise, if email is disabled, there is no way to see the password hint
- show_password_hint: bool, true, def, true;
-
- /// settings |> Domain URL |> This needs to be set to the URL used to access the server, including 'http[s]://' and port, if it's different than the default. Some server functions don't work correctly without this value
- domain: String, true, def, "http://localhost".to_string();
- /// private |> Domain set
- domain_set: bool, false, def, false;
-
- /// settings |> Reload templates (Dev) |> When this is set to true, the templates get reloaded with every request. ONLY use this during development, as it can slow down the server
- reload_templates: bool, true, def, false;
-
- /// log |> Enable extended logging
- extended_logging: bool, false, def, true;
- /// log |> Log file path
- log_file: String, false, option;
-
- /// settings |> Admin page token |> The token used to authenticate in this very same page. Changing it here won't deauthorize the current session
- admin_token: String, true, option;
-
- /// yubico |> Yubico Client ID
- yubico_client_id: String, true, option;
- /// yubico |> Yubico secret Key
- yubico_secret_key: String, true, option;
- /// yubico |> Yubico Server
- yubico_server: String, true, option;
-
- // TODO: Remove SMTP from name once groups work
- /// mail |> SMTP Host
- smtp_host: String, true, option;
- /// mail |> Enable SMTP SSL
- smtp_ssl: bool, true, def, true;
- /// mail |> SMTP Port
- smtp_port: u16, true, auto, |c| if c.smtp_ssl {587} else {25};
- /// mail |> SMTP From Address
- smtp_from: String, true, def, String::new();
- /// mail |> SMTP From Name
- smtp_from_name: String, true, def, "Bitwarden_RS".to_string();
- /// mail |> SMTP Username
- smtp_username: String, true, option;
- /// mail |> SMTP Password
- smtp_password: String, true, option;
+ folders {
+ /// Data folder |> Main data folder
+ data_folder: String, false, def, "data".to_string();
+
+ /// Database URL
+ database_url: String, false, auto, |c| format!("{}/{}", c.data_folder, "db.sqlite3");
+ /// Icon chache folder
+ icon_cache_folder: String, false, auto, |c| format!("{}/{}", c.data_folder, "icon_cache");
+ /// Attachments folder
+ attachments_folder: String, false, auto, |c| format!("{}/{}", c.data_folder, "attachments");
+ /// Templates folder
+ templates_folder: String, false, auto, |c| format!("{}/{}", c.data_folder, "templates");
+ /// Session JWT key
+ rsa_key_filename: String, false, auto, |c| format!("{}/{}", c.data_folder, "rsa_key");
+ /// Web vault folder
+ web_vault_folder: String, false, def, "web-vault/".to_string();
+ },
+ ws {
+ /// Enable websocket notifications
+ websocket_enabled: bool, false, def, false;
+ /// Websocket address
+ websocket_address: String, false, def, "0.0.0.0".to_string();
+ /// Websocket port
+ websocket_port: u16, false, def, 3012;
+ },
+
+ /// General settings
+ settings {
+ /// Domain URL |> This needs to be set to the URL used to access the server, including 'http[s]://' and port, if it's different than the default. Some server functions don't work correctly without this value
+ domain: String, true, def, "http://localhost".to_string();
+ /// PRIVATE |> Domain set
+ domain_set: bool, false, def, false;
+ /// Enable web vault
+ web_vault_enabled: bool, false, def, true;
+
+ /// Disable icon downloads |> Set to true to disable icon downloading, this would still serve icons from $ICON_CACHE_FOLDER,
+ /// but it won't produce any external network request. Needs to set $ICON_CACHE_TTL to 0,
+ /// otherwise it will delete them and they won't be downloaded again.
+ disable_icon_download: bool, true, def, false;
+ /// Allow new signups |> Controls if new users can register. Note that while this is disabled, users could still be invited
+ signups_allowed: bool, true, def, true;
+ /// Allow invitations |> Controls whether users can be invited by organization admins, even when signups are disabled
+ invitations_allowed: bool, true, def, true;
+ /// Password iterations |> Number of server-side passwords hashing iterations. The changes only apply when a user changes their password. Not recommended to lower the value
+ password_iterations: i32, true, def, 100_000;
+ /// Show password hints |> Controls if the password hint should be shown directly in the web page. Otherwise, if email is disabled, there is no way to see the password hint
+ show_password_hint: bool, true, def, true;
+
+ /// Admin page token |> The token used to authenticate in this very same page. Changing it here won't deauthorize the current session
+ admin_token: String, true, option;
+ },
+
+ /// Advanced settings
+ advanced {
+ /// Positive icon cache expiry |> Number of seconds to consider that an already cached icon is fresh. After this period, the icon will be redownloaded
+ icon_cache_ttl: u64, true, def, 2_592_000;
+ /// Negative icon cache expiry |> Number of seconds before trying to download an icon that failed again.
+ icon_cache_negttl: u64, true, def, 259_200;
+
+ /// Reload templates (Dev) |> When this is set to true, the templates get reloaded with every request. ONLY use this during development, as it can slow down the server
+ reload_templates: bool, true, def, false;
+
+ /// Enable extended logging
+ extended_logging: bool, false, def, true;
+ /// Log file path
+ log_file: String, false, option;
+ },
+
+ /// Yubikey settings
+ yubico {
+ /// Client ID
+ yubico_client_id: String, true, option;
+ /// Secret Key
+ yubico_secret_key: String, true, option;
+ /// Server
+ yubico_server: String, true, option;
+ },
+
+ /// SMTP Email Settings
+ smtp {
+ /// Host
+ smtp_host: String, true, option;
+ /// Enable SSL
+ smtp_ssl: bool, true, def, true;
+ /// Port
+ smtp_port: u16, true, auto, |c| if c.smtp_ssl {587} else {25};
+ /// From Address
+ smtp_from: String, true, def, String::new();
+ /// From Name
+ smtp_from_name: String, true, def, "Bitwarden_RS".to_string();
+ /// Username
+ smtp_username: String, true, option;
+ /// Password
+ smtp_password: String, true, option;
+ },
}
fn validate_config(cfg: &ConfigItems) -> Result<(), Error> {
diff --git a/src/static/templates/admin/base.hbs b/src/static/templates/admin/base.hbs
index a715f1b6..a718ed34 100644
--- a/src/static/templates/admin/base.hbs
+++ b/src/static/templates/admin/base.hbs
@@ -14,7 +14,8 @@
crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/identicon.js/2.3.3/identicon.min.js" integrity="sha256-nYoL3nK/HA1e1pJvLwNPnpKuKG9q89VFX862r5aohmA="
crossorigin="anonymous"></script>
-
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.2.1/js/bootstrap.bundle.min.js" integrity="sha256-MSYVjWgrr6UL/9eQfQvOyt6/gsxb6dpwI1zqM5DbLCs="
+ crossorigin="anonymous"></script>
<style>
body {
padding-top: 70px;
@@ -41,7 +42,7 @@
</ul>
</div>
</nav>
-
+
{{> (page_content) }}
</body>
diff --git a/src/static/templates/admin/page.hbs b/src/static/templates/admin/page.hbs
index 8a041b97..5a167fa7 100644
--- a/src/static/templates/admin/page.hbs
+++ b/src/static/templates/admin/page.hbs
@@ -54,31 +54,41 @@
</div>
</div>
- <div id="config-block" class="align-items-center p-3 mb-3 text-white-50 bg-secondary rounded shadow">
+ <div id="config-block" class="align-items-center p-3 mb-3 bg-secondary rounded shadow">
<div>
- <h6 class="text-white">Configuration</h6>
- <form class="form" id="config-form">
+ <h6 class="text-white mb-3">Configuration</h6>
+ <form class="form accordion" id="config-form">
{{#each config}}
- {{#if editable}}
- <div class="form-group row" title="{{doc.description}}">
- {{#case type "text" "number"}}
- <label for="input_{{name}}" class="col-sm-3 col-form-label">{{doc.name}}</label>
- <div class="col-sm-8">
- <input class="form-control" id="input_{{name}}" type="{{type}}" name="{{name}}" value="{{value}}"
- {{#if default}} placeholder="Default: {{default}}" {{/if}}>
- </div>
- {{/case}}
- {{#case type "checkbox"}}
- <div class="col-sm-3">{{doc.name}}</div>
- <div class="col-sm-8">
- <div class="form-check">
- <input class="form-check-input" type="checkbox" id="input_{{name}}" name="{{name}}"
- {{#if value}} checked {{/if}}>
+ {{#if groupdoc}}
+ <div class="card bg-light mb-3">
+ <div class="card-header"><button class="btn btn-link collapsed" type="button" data-toggle="collapse"
+ data-target="#g_{{group}}">{{groupdoc}}</button></div>
+ <div id="g_{{group}}" class="card-body collapse" data-parent="#config-form">
+ {{#each elements}}
+ {{#if editable}}
+ <div class="form-group row" title="{{doc.description}}">
+ {{#case type "text" "number"}}
+ <label for="input_{{name}}" class="col-sm-3 col-form-label">{{doc.name}}</label>
+ <div class="col-sm-8">
+ <input class="form-control" id="input_{{name}}" type="{{type}}" name="{{name}}" value="{{value}}"
+ {{#if default}} placeholder="Default: {{default}}" {{/if}}>
+ </div>
+ {{/case}}
+ {{#case type "checkbox"}}
+ <div class="col-sm-3">{{doc.name}}</div>
+ <div class="col-sm-8">
+ <div class="form-check">
+ <input class="form-check-input" type="checkbox" id="input_{{name}}" name="{{name}}"
+ {{#if value}} checked {{/if}}>
- <label class="form-check-label" for="input_{{name}}"> Default: {{default}} </label>
+ <label class="form-check-label" for="input_{{name}}"> Default: {{default}} </label>
+ </div>
+ </div>
+ {{/case}}
</div>
+ {{/if}}
+ {{/each}}
</div>
- {{/case}}
</div>
{{/if}}
{{/each}}