Browse Source

Merge branch 'main' into Use-64bit-timestamps-in-TOTP

pull/4355/head
Daniel García 1 year ago
committed by GitHub
parent
commit
ad12b4054d
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 22
      .env.template
  2. 8
      .github/workflows/release.yml
  3. 482
      Cargo.lock
  4. 31
      Cargo.toml
  5. 2
      src/api/core/accounts.rs
  6. 2
      src/api/core/events.rs
  7. 2
      src/api/core/two_factor/authenticator.rs
  8. 2
      src/api/mod.rs
  9. 228
      src/api/notifications.rs
  10. 4
      src/api/web.rs
  11. 59
      src/auth.rs
  12. 23
      src/config.rs
  13. 2
      src/db/models/attachment.rs
  14. 2
      src/error.rs
  15. 37
      src/main.rs
  16. 10
      src/static/scripts/bootstrap.bundle.js
  17. 81
      src/static/scripts/bootstrap.css
  18. 350
      src/static/scripts/datatables.css
  19. 12153
      src/static/scripts/datatables.js
  20. 48
      src/static/scripts/jquery-3.7.1.slim.js
  21. 2
      src/static/templates/admin/organizations.hbs
  22. 2
      src/static/templates/admin/users.hbs
  23. 2
      src/static/templates/email/change_email.hbs
  24. 2
      src/static/templates/email/change_email.html.hbs
  25. 42
      src/util.rs
  26. 14
      tools/global_domains.py

22
.env.template

@ -84,12 +84,8 @@
### WebSocket ### ### WebSocket ###
################# #################
## Enables websocket notifications ## Enable websocket notifications
# WEBSOCKET_ENABLED=false # ENABLE_WEBSOCKET=true
## Controls the WebSocket server address and port
# WEBSOCKET_ADDRESS=0.0.0.0
# WEBSOCKET_PORT=3012
########################## ##########################
### Push notifications ### ### Push notifications ###
@ -477,12 +473,19 @@
# SMTP_HOST=smtp.domain.tld # SMTP_HOST=smtp.domain.tld
# SMTP_FROM=vaultwarden@domain.tld # SMTP_FROM=vaultwarden@domain.tld
# SMTP_FROM_NAME=Vaultwarden # SMTP_FROM_NAME=Vaultwarden
# SMTP_SECURITY=starttls # ("starttls", "force_tls", "off") Enable a secure connection. Default is "starttls" (Explicit - ports 587 or 25), "force_tls" (Implicit - port 465) or "off", no encryption (port 25)
# SMTP_PORT=587 # Ports 587 (submission) and 25 (smtp) are standard without encryption and with encryption via STARTTLS (Explicit TLS). Port 465 (submissions) is used for encrypted submission (Implicit TLS).
# SMTP_USERNAME=username # SMTP_USERNAME=username
# SMTP_PASSWORD=password # SMTP_PASSWORD=password
# SMTP_TIMEOUT=15 # SMTP_TIMEOUT=15
## Choose the type of secure connection for SMTP. The default is "starttls".
## The available options are:
## - "starttls": The default port is 587.
## - "force_tls": The default port is 465.
## - "off": The default port is 25.
## Ports 587 (submission) and 25 (smtp) are standard without encryption and with encryption via STARTTLS (Explicit TLS). Port 465 (submissions) is used for encrypted submission (Implicit TLS).
# SMTP_SECURITY=starttls
# SMTP_PORT=587
# Whether to send mail via the `sendmail` command # Whether to send mail via the `sendmail` command
# USE_SENDMAIL=false # USE_SENDMAIL=false
# Which sendmail command to use. The one found in the $PATH is used if not specified. # Which sendmail command to use. The one found in the $PATH is used if not specified.
@ -524,7 +527,8 @@
## Rocket specific settings ## Rocket specific settings
## See https://rocket.rs/v0.5/guide/configuration/ for more details. ## See https://rocket.rs/v0.5/guide/configuration/ for more details.
# ROCKET_ADDRESS=0.0.0.0 # ROCKET_ADDRESS=0.0.0.0
# ROCKET_PORT=80 # Defaults to 80 in the Docker images, or 8000 otherwise. ## The default port is 8000, unless running in a Docker container, in which case it is 80.
# ROCKET_PORT=8000
# ROCKET_TLS={certs="/path/to/certs.pem",key="/path/to/key.pem"} # ROCKET_TLS={certs="/path/to/certs.pem",key="/path/to/key.pem"}

8
.github/workflows/release.yml

@ -229,28 +229,28 @@ jobs:
# Upload artifacts to Github Actions # Upload artifacts to Github Actions
- name: "Upload amd64 artifact" - name: "Upload amd64 artifact"
uses: actions/upload-artifact@26f96dfa697d77e81fd5907df203aa23a56210a8 # v4.3.0 uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
if: ${{ matrix.base_image == 'alpine' }} if: ${{ matrix.base_image == 'alpine' }}
with: with:
name: vaultwarden-${{ env.SOURCE_VERSION }}-linux-amd64 name: vaultwarden-${{ env.SOURCE_VERSION }}-linux-amd64
path: vaultwarden-amd64 path: vaultwarden-amd64
- name: "Upload arm64 artifact" - name: "Upload arm64 artifact"
uses: actions/upload-artifact@26f96dfa697d77e81fd5907df203aa23a56210a8 # v4.3.0 uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
if: ${{ matrix.base_image == 'alpine' }} if: ${{ matrix.base_image == 'alpine' }}
with: with:
name: vaultwarden-${{ env.SOURCE_VERSION }}-linux-arm64 name: vaultwarden-${{ env.SOURCE_VERSION }}-linux-arm64
path: vaultwarden-arm64 path: vaultwarden-arm64
- name: "Upload armv7 artifact" - name: "Upload armv7 artifact"
uses: actions/upload-artifact@26f96dfa697d77e81fd5907df203aa23a56210a8 # v4.3.0 uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
if: ${{ matrix.base_image == 'alpine' }} if: ${{ matrix.base_image == 'alpine' }}
with: with:
name: vaultwarden-${{ env.SOURCE_VERSION }}-linux-armv7 name: vaultwarden-${{ env.SOURCE_VERSION }}-linux-armv7
path: vaultwarden-armv7 path: vaultwarden-armv7
- name: "Upload armv6 artifact" - name: "Upload armv6 artifact"
uses: actions/upload-artifact@26f96dfa697d77e81fd5907df203aa23a56210a8 # v4.3.0 uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
if: ${{ matrix.base_image == 'alpine' }} if: ${{ matrix.base_image == 'alpine' }}
with: with:
name: vaultwarden-${{ env.SOURCE_VERSION }}-linux-armv6 name: vaultwarden-${{ env.SOURCE_VERSION }}-linux-armv6

482
Cargo.lock

File diff suppressed because it is too large

31
Cargo.toml

@ -40,7 +40,7 @@ syslog = "6.1.0"
[dependencies] [dependencies]
# Logging # Logging
log = "0.4.20" log = "0.4.21"
fern = { version = "0.6.2", features = ["syslog-6", "reopen-1"] } fern = { version = "0.6.2", features = ["syslog-6", "reopen-1"] }
tracing = { version = "0.1.40", features = ["log"] } # Needed to have lettre and webauthn-rs trace logging to work tracing = { version = "0.1.40", features = ["log"] } # Needed to have lettre and webauthn-rs trace logging to work
@ -53,14 +53,13 @@ once_cell = "1.19.0"
# Numerical libraries # Numerical libraries
num-traits = "0.2.18" num-traits = "0.2.18"
num-derive = "0.4.2" num-derive = "0.4.2"
bigdecimal = "0.4.2" bigdecimal = "0.4.3"
# Web framework # Web framework
rocket = { version = "0.5.0", features = ["tls", "json"], default-features = false } rocket = { version = "0.5.0", features = ["tls", "json"], default-features = false }
rocket_ws = { version ="0.1.0" } rocket_ws = { version ="0.1.0" }
# WebSockets libraries # WebSockets libraries
tokio-tungstenite = "0.20.1"
rmpv = "1.0.1" # MessagePack library rmpv = "1.0.1" # MessagePack library
# Concurrent HashMap used for WebSocket messaging and favicons # Concurrent HashMap used for WebSocket messaging and favicons
@ -71,11 +70,11 @@ futures = "0.3.30"
tokio = { version = "1.36.0", features = ["rt-multi-thread", "fs", "io-util", "parking_lot", "time", "signal"] } tokio = { version = "1.36.0", features = ["rt-multi-thread", "fs", "io-util", "parking_lot", "time", "signal"] }
# A generic serialization/deserialization framework # A generic serialization/deserialization framework
serde = { version = "1.0.196", features = ["derive"] } serde = { version = "1.0.197", features = ["derive"] }
serde_json = "1.0.113" serde_json = "1.0.114"
# A safe, extensible ORM and Query builder # A safe, extensible ORM and Query builder
diesel = { version = "2.1.4", features = ["chrono", "r2d2", "numeric"] } diesel = { version = "2.1.5", features = ["chrono", "r2d2", "numeric"] }
diesel_migrations = "2.1.0" diesel_migrations = "2.1.0"
diesel_logger = { version = "0.3.0", optional = true } diesel_logger = { version = "0.3.0", optional = true }
@ -84,14 +83,14 @@ libsqlite3-sys = { version = "0.27.0", features = ["bundled"], optional = true }
# Crypto-related libraries # Crypto-related libraries
rand = { version = "0.8.5", features = ["small_rng"] } rand = { version = "0.8.5", features = ["small_rng"] }
ring = "0.17.7" ring = "0.17.8"
# UUID generation # UUID generation
uuid = { version = "1.7.0", features = ["v4"] } uuid = { version = "1.7.0", features = ["v4"] }
# Date and time libraries # Date and time libraries
chrono = { version = "0.4.33", features = ["clock", "serde"], default-features = false } chrono = { version = "0.4.34", features = ["clock", "serde"], default-features = false }
chrono-tz = "0.8.5" chrono-tz = "0.8.6"
time = "0.3.34" time = "0.3.34"
# Job scheduler # Job scheduler
@ -124,7 +123,7 @@ email_address = "0.2.4"
handlebars = { version = "5.1.0", features = ["dir_source"] } handlebars = { version = "5.1.0", features = ["dir_source"] }
# HTTP client (Used for favicons, version check, DUO and HIBP API) # HTTP client (Used for favicons, version check, DUO and HIBP API)
reqwest = { version = "0.11.24", features = ["stream", "json", "gzip", "brotli", "socks", "cookies", "trust-dns", "native-tls-alpn"] } reqwest = { version = "0.11.26", features = ["stream", "json", "gzip", "brotli", "socks", "cookies", "trust-dns", "native-tls-alpn"] }
# Favicon extraction libraries # Favicon extraction libraries
html5gum = "0.5.7" html5gum = "0.5.7"
@ -133,24 +132,24 @@ data-url = "0.3.1"
bytes = "1.5.0" bytes = "1.5.0"
# Cache function results (Used for version check and favicon fetching) # Cache function results (Used for version check and favicon fetching)
cached = { version = "0.48.1", features = ["async"] } cached = { version = "0.49.2", features = ["async"] }
# Used for custom short lived cookie jar during favicon extraction # Used for custom short lived cookie jar during favicon extraction
cookie = "0.17.0" cookie = "0.18.0"
cookie_store = "0.20.0" cookie_store = "0.21.0"
# Used by U2F, JWT and PostgreSQL # Used by U2F, JWT and PostgreSQL
openssl = "0.10.63" openssl = "0.10.64"
# CLI argument parsing # CLI argument parsing
pico-args = "0.5.0" pico-args = "0.5.0"
# Macro ident concatenation # Macro ident concatenation
paste = "1.0.14" paste = "1.0.14"
governor = "0.6.0" governor = "0.6.3"
# Check client versions for specific features. # Check client versions for specific features.
semver = "1.0.21" semver = "1.0.22"
# Allow overriding the default memory allocator # Allow overriding the default memory allocator
# Mainly used for the musl builds, since the default musl malloc is very slow # Mainly used for the musl builds, since the default musl malloc is very slow

2
src/api/core/accounts.rs

@ -559,6 +559,8 @@ async fn post_email_token(data: JsonUpcase<EmailTokenData>, headers: Headers, mu
if let Err(e) = mail::send_change_email(&data.NewEmail, &token).await { if let Err(e) = mail::send_change_email(&data.NewEmail, &token).await {
error!("Error sending change-email email: {:#?}", e); error!("Error sending change-email email: {:#?}", e);
} }
} else {
debug!("Email change request for user ({}) to email ({}) with token ({})", user.uuid, data.NewEmail, token);
} }
user.email_new = Some(data.NewEmail); user.email_new = Some(data.NewEmail);

2
src/api/core/events.rs

@ -289,7 +289,7 @@ async fn _log_event(
let mut event = Event::new(event_type, event_date); let mut event = Event::new(event_type, event_date);
match event_type { match event_type {
// 1000..=1099 Are user events, they need to be logged via log_user_event() // 1000..=1099 Are user events, they need to be logged via log_user_event()
// Collection Events // Cipher Events
1100..=1199 => { 1100..=1199 => {
event.cipher_uuid = Some(String::from(source_uuid)); event.cipher_uuid = Some(String::from(source_uuid));
} }

2
src/api/core/two_factor/authenticator.rs

@ -156,7 +156,7 @@ pub async fn validate_totp_code(
let time = (current_timestamp + step * 30i64) as u64; let time = (current_timestamp + step * 30i64) as u64;
let generated = totp_custom::<Sha1>(30, 6, &decoded_secret, time); let generated = totp_custom::<Sha1>(30, 6, &decoded_secret, time);
// Check the the given code equals the generated and if the time_step is larger then the one last used. // Check the given code equals the generated and if the time_step is larger then the one last used.
if generated == totp_code && time_step > twofactor.last_used { if generated == totp_code && time_step > twofactor.last_used {
// If the step does not equals 0 the time is drifted either server or client side. // If the step does not equals 0 the time is drifted either server or client side.
if step != 0 { if step != 0 {

2
src/api/mod.rs

@ -23,7 +23,7 @@ pub use crate::api::{
icons::routes as icons_routes, icons::routes as icons_routes,
identity::routes as identity_routes, identity::routes as identity_routes,
notifications::routes as notifications_routes, notifications::routes as notifications_routes,
notifications::{start_notification_server, AnonymousNotify, Notify, UpdateType, WS_ANONYMOUS_SUBSCRIPTIONS}, notifications::{AnonymousNotify, Notify, UpdateType, WS_ANONYMOUS_SUBSCRIPTIONS, WS_USERS},
push::{ push::{
push_cipher_update, push_folder_update, push_logout, push_send_update, push_user_update, register_push_device, push_cipher_update, push_folder_update, push_logout, push_send_update, push_user_update, register_push_device,
unregister_push_device, unregister_push_device,

228
src/api/notifications.rs

@ -1,23 +1,11 @@
use std::{ use std::{net::IpAddr, sync::Arc, time::Duration};
net::{IpAddr, SocketAddr},
sync::Arc,
time::Duration,
};
use chrono::{NaiveDateTime, Utc}; use chrono::{NaiveDateTime, Utc};
use rmpv::Value; use rmpv::Value;
use rocket::{ use rocket::{futures::StreamExt, Route};
futures::{SinkExt, StreamExt}, use tokio::sync::mpsc::Sender;
Route,
}; use rocket_ws::{Message, WebSocket};
use tokio::{
net::{TcpListener, TcpStream},
sync::mpsc::Sender,
};
use tokio_tungstenite::{
accept_hdr_async,
tungstenite::{handshake, Message},
};
use crate::{ use crate::{
auth::{ClientIp, WsAccessTokenHeader}, auth::{ClientIp, WsAccessTokenHeader},
@ -30,7 +18,7 @@ use crate::{
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
static WS_USERS: Lazy<Arc<WebSocketUsers>> = Lazy::new(|| { pub static WS_USERS: Lazy<Arc<WebSocketUsers>> = Lazy::new(|| {
Arc::new(WebSocketUsers { Arc::new(WebSocketUsers {
map: Arc::new(dashmap::DashMap::new()), map: Arc::new(dashmap::DashMap::new()),
}) })
@ -47,8 +35,15 @@ use super::{
push_send_update, push_user_update, push_send_update, push_user_update,
}; };
static NOTIFICATIONS_DISABLED: Lazy<bool> = Lazy::new(|| !CONFIG.enable_websocket() && !CONFIG.push_enabled());
pub fn routes() -> Vec<Route> { pub fn routes() -> Vec<Route> {
routes![websockets_hub, anonymous_websockets_hub] if CONFIG.enable_websocket() {
routes![websockets_hub, anonymous_websockets_hub]
} else {
info!("WebSocket are disabled, realtime sync functionality will not work!");
routes![]
}
} }
#[derive(FromForm, Debug)] #[derive(FromForm, Debug)]
@ -108,7 +103,7 @@ impl Drop for WSAnonymousEntryMapGuard {
#[get("/hub?<data..>")] #[get("/hub?<data..>")]
fn websockets_hub<'r>( fn websockets_hub<'r>(
ws: rocket_ws::WebSocket, ws: WebSocket,
data: WsAccessToken, data: WsAccessToken,
ip: ClientIp, ip: ClientIp,
header_token: WsAccessTokenHeader, header_token: WsAccessTokenHeader,
@ -192,11 +187,7 @@ fn websockets_hub<'r>(
} }
#[get("/anonymous-hub?<token..>")] #[get("/anonymous-hub?<token..>")]
fn anonymous_websockets_hub<'r>( fn anonymous_websockets_hub<'r>(ws: WebSocket, token: String, ip: ClientIp) -> Result<rocket_ws::Stream!['r], Error> {
ws: rocket_ws::WebSocket,
token: String,
ip: ClientIp,
) -> Result<rocket_ws::Stream!['r], Error> {
let addr = ip.ip; let addr = ip.ip;
info!("Accepting Anonymous Rocket WS connection from {addr}"); info!("Accepting Anonymous Rocket WS connection from {addr}");
@ -349,13 +340,19 @@ impl WebSocketUsers {
// NOTE: The last modified date needs to be updated before calling these methods // NOTE: The last modified date needs to be updated before calling these methods
pub async fn send_user_update(&self, ut: UpdateType, user: &User) { pub async fn send_user_update(&self, ut: UpdateType, user: &User) {
// Skip any processing if both WebSockets and Push are not active
if *NOTIFICATIONS_DISABLED {
return;
}
let data = create_update( let data = create_update(
vec![("UserId".into(), user.uuid.clone().into()), ("Date".into(), serialize_date(user.updated_at))], vec![("UserId".into(), user.uuid.clone().into()), ("Date".into(), serialize_date(user.updated_at))],
ut, ut,
None, None,
); );
self.send_update(&user.uuid, &data).await; if CONFIG.enable_websocket() {
self.send_update(&user.uuid, &data).await;
}
if CONFIG.push_enabled() { if CONFIG.push_enabled() {
push_user_update(ut, user); push_user_update(ut, user);
@ -363,13 +360,19 @@ impl WebSocketUsers {
} }
pub async fn send_logout(&self, user: &User, acting_device_uuid: Option<String>) { pub async fn send_logout(&self, user: &User, acting_device_uuid: Option<String>) {
// Skip any processing if both WebSockets and Push are not active
if *NOTIFICATIONS_DISABLED {
return;
}
let data = create_update( let data = create_update(
vec![("UserId".into(), user.uuid.clone().into()), ("Date".into(), serialize_date(user.updated_at))], vec![("UserId".into(), user.uuid.clone().into()), ("Date".into(), serialize_date(user.updated_at))],
UpdateType::LogOut, UpdateType::LogOut,
acting_device_uuid.clone(), acting_device_uuid.clone(),
); );
self.send_update(&user.uuid, &data).await; if CONFIG.enable_websocket() {
self.send_update(&user.uuid, &data).await;
}
if CONFIG.push_enabled() { if CONFIG.push_enabled() {
push_logout(user, acting_device_uuid); push_logout(user, acting_device_uuid);
@ -383,6 +386,10 @@ impl WebSocketUsers {
acting_device_uuid: &String, acting_device_uuid: &String,
conn: &mut DbConn, conn: &mut DbConn,
) { ) {
// Skip any processing if both WebSockets and Push are not active
if *NOTIFICATIONS_DISABLED {
return;
}
let data = create_update( let data = create_update(
vec![ vec![
("Id".into(), folder.uuid.clone().into()), ("Id".into(), folder.uuid.clone().into()),
@ -393,7 +400,9 @@ impl WebSocketUsers {
Some(acting_device_uuid.into()), Some(acting_device_uuid.into()),
); );
self.send_update(&folder.user_uuid, &data).await; if CONFIG.enable_websocket() {
self.send_update(&folder.user_uuid, &data).await;
}
if CONFIG.push_enabled() { if CONFIG.push_enabled() {
push_folder_update(ut, folder, acting_device_uuid, conn).await; push_folder_update(ut, folder, acting_device_uuid, conn).await;
@ -409,6 +418,10 @@ impl WebSocketUsers {
collection_uuids: Option<Vec<String>>, collection_uuids: Option<Vec<String>>,
conn: &mut DbConn, conn: &mut DbConn,
) { ) {
// Skip any processing if both WebSockets and Push are not active
if *NOTIFICATIONS_DISABLED {
return;
}
let org_uuid = convert_option(cipher.organization_uuid.clone()); let org_uuid = convert_option(cipher.organization_uuid.clone());
// Depending if there are collections provided or not, we need to have different values for the following variables. // Depending if there are collections provided or not, we need to have different values for the following variables.
// The user_uuid should be `null`, and the revision date should be set to now, else the clients won't sync the collection change. // The user_uuid should be `null`, and the revision date should be set to now, else the clients won't sync the collection change.
@ -434,8 +447,10 @@ impl WebSocketUsers {
Some(acting_device_uuid.into()), Some(acting_device_uuid.into()),
); );
for uuid in user_uuids { if CONFIG.enable_websocket() {
self.send_update(uuid, &data).await; for uuid in user_uuids {
self.send_update(uuid, &data).await;
}
} }
if CONFIG.push_enabled() && user_uuids.len() == 1 { if CONFIG.push_enabled() && user_uuids.len() == 1 {
@ -451,6 +466,10 @@ impl WebSocketUsers {
acting_device_uuid: &String, acting_device_uuid: &String,
conn: &mut DbConn, conn: &mut DbConn,
) { ) {
// Skip any processing if both WebSockets and Push are not active
if *NOTIFICATIONS_DISABLED {
return;
}
let user_uuid = convert_option(send.user_uuid.clone()); let user_uuid = convert_option(send.user_uuid.clone());
let data = create_update( let data = create_update(
@ -463,8 +482,10 @@ impl WebSocketUsers {
None, None,
); );
for uuid in user_uuids { if CONFIG.enable_websocket() {
self.send_update(uuid, &data).await; for uuid in user_uuids {
self.send_update(uuid, &data).await;
}
} }
if CONFIG.push_enabled() && user_uuids.len() == 1 { if CONFIG.push_enabled() && user_uuids.len() == 1 {
push_send_update(ut, send, acting_device_uuid, conn).await; push_send_update(ut, send, acting_device_uuid, conn).await;
@ -478,12 +499,18 @@ impl WebSocketUsers {
acting_device_uuid: &String, acting_device_uuid: &String,
conn: &mut DbConn, conn: &mut DbConn,
) { ) {
// Skip any processing if both WebSockets and Push are not active
if *NOTIFICATIONS_DISABLED {
return;
}
let data = create_update( let data = create_update(
vec![("Id".into(), auth_request_uuid.clone().into()), ("UserId".into(), user_uuid.clone().into())], vec![("Id".into(), auth_request_uuid.clone().into()), ("UserId".into(), user_uuid.clone().into())],
UpdateType::AuthRequest, UpdateType::AuthRequest,
Some(acting_device_uuid.to_string()), Some(acting_device_uuid.to_string()),
); );
self.send_update(user_uuid, &data).await; if CONFIG.enable_websocket() {
self.send_update(user_uuid, &data).await;
}
if CONFIG.push_enabled() { if CONFIG.push_enabled() {
push_auth_request(user_uuid.to_string(), auth_request_uuid.to_string(), conn).await; push_auth_request(user_uuid.to_string(), auth_request_uuid.to_string(), conn).await;
@ -497,12 +524,18 @@ impl WebSocketUsers {
approving_device_uuid: String, approving_device_uuid: String,
conn: &mut DbConn, conn: &mut DbConn,
) { ) {
// Skip any processing if both WebSockets and Push are not active
if *NOTIFICATIONS_DISABLED {
return;
}
let data = create_update( let data = create_update(
vec![("Id".into(), auth_response_uuid.to_owned().into()), ("UserId".into(), user_uuid.clone().into())], vec![("Id".into(), auth_response_uuid.to_owned().into()), ("UserId".into(), user_uuid.clone().into())],
UpdateType::AuthRequestResponse, UpdateType::AuthRequestResponse,
approving_device_uuid.clone().into(), approving_device_uuid.clone().into(),
); );
self.send_update(auth_response_uuid, &data).await; if CONFIG.enable_websocket() {
self.send_update(auth_response_uuid, &data).await;
}
if CONFIG.push_enabled() { if CONFIG.push_enabled() {
push_auth_response(user_uuid.to_string(), auth_response_uuid.to_string(), approving_device_uuid, conn) push_auth_response(user_uuid.to_string(), auth_response_uuid.to_string(), approving_device_uuid, conn)
@ -526,6 +559,9 @@ impl AnonymousWebSocketSubscriptions {
} }
pub async fn send_auth_response(&self, user_uuid: &String, auth_response_uuid: &str) { pub async fn send_auth_response(&self, user_uuid: &String, auth_response_uuid: &str) {
if !CONFIG.enable_websocket() {
return;
}
let data = create_anonymous_update( let data = create_anonymous_update(
vec![("Id".into(), auth_response_uuid.to_owned().into()), ("UserId".into(), user_uuid.clone().into())], vec![("Id".into(), auth_response_uuid.to_owned().into()), ("UserId".into(), user_uuid.clone().into())],
UpdateType::AuthRequestResponse, UpdateType::AuthRequestResponse,
@ -620,127 +656,3 @@ pub enum UpdateType {
pub type Notify<'a> = &'a rocket::State<Arc<WebSocketUsers>>; pub type Notify<'a> = &'a rocket::State<Arc<WebSocketUsers>>;
pub type AnonymousNotify<'a> = &'a rocket::State<Arc<AnonymousWebSocketSubscriptions>>; pub type AnonymousNotify<'a> = &'a rocket::State<Arc<AnonymousWebSocketSubscriptions>>;
pub fn start_notification_server() -> Arc<WebSocketUsers> {
let users = Arc::clone(&WS_USERS);
if CONFIG.websocket_enabled() {
let users2 = Arc::<WebSocketUsers>::clone(&users);
tokio::spawn(async move {
let addr = (CONFIG.websocket_address(), CONFIG.websocket_port());
info!("Starting WebSockets server on {}:{}", addr.0, addr.1);
let listener = TcpListener::bind(addr).await.expect("Can't listen on websocket port");
let (shutdown_tx, mut shutdown_rx) = tokio::sync::oneshot::channel::<()>();
CONFIG.set_ws_shutdown_handle(shutdown_tx);
loop {
tokio::select! {
Ok((stream, addr)) = listener.accept() => {
tokio::spawn(handle_connection(stream, Arc::<WebSocketUsers>::clone(&users2), addr));
}
_ = &mut shutdown_rx => {
break;
}
}
}
info!("Shutting down WebSockets server!")
});
}
users
}
async fn handle_connection(stream: TcpStream, users: Arc<WebSocketUsers>, addr: SocketAddr) -> Result<(), Error> {
let mut user_uuid: Option<String> = None;
info!("Accepting WS connection from {addr}");
// Accept connection, do initial handshake, validate auth token and get the user ID
use handshake::server::{Request, Response};
let mut stream = accept_hdr_async(stream, |req: &Request, res: Response| {
if let Some(token) = get_request_token(req) {
if let Ok(claims) = crate::auth::decode_login(&token) {
user_uuid = Some(claims.sub);
return Ok(res);
}
}
Err(Response::builder().status(401).body(None).unwrap())
})
.await?;
let user_uuid = user_uuid.expect("User UUID should be set after the handshake");
let (mut rx, guard) = {
// Add a channel to send messages to this client to the map
let entry_uuid = uuid::Uuid::new_v4();
let (tx, rx) = tokio::sync::mpsc::channel::<Message>(100);
users.map.entry(user_uuid.clone()).or_default().push((entry_uuid, tx));
// Once the guard goes out of scope, the connection will have been closed and the entry will be deleted from the map
(rx, WSEntryMapGuard::new(users, user_uuid, entry_uuid, addr.ip()))
};
let _guard = guard;
let mut interval = tokio::time::interval(Duration::from_secs(15));
loop {
tokio::select! {
res = stream.next() => {
match res {
Some(Ok(message)) => {
match message {
// Respond to any pings
Message::Ping(ping) => stream.send(Message::Pong(ping)).await?,
Message::Pong(_) => {/* Ignored */},
// We should receive an initial message with the protocol and version, and we will reply to it
Message::Text(ref message) => {
let msg = message.strip_suffix(RECORD_SEPARATOR as char).unwrap_or(message);
if serde_json::from_str(msg).ok() == Some(INITIAL_MESSAGE) {
stream.send(Message::binary(INITIAL_RESPONSE)).await?;
continue;
}
}
// Just echo anything else the client sends
_ => stream.send(message).await?,
}
}
_ => break,
}
}
res = rx.recv() => {
match res {
Some(res) => stream.send(res).await?,
None => break,
}
}
_ = interval.tick() => stream.send(Message::Ping(create_ping())).await?
}
}
Ok(())
}
fn get_request_token(req: &handshake::server::Request) -> Option<String> {
const ACCESS_TOKEN_KEY: &str = "access_token=";
if let Some(Ok(auth)) = req.headers().get("Authorization").map(|a| a.to_str()) {
if let Some(token_part) = auth.strip_prefix("Bearer ") {
return Some(token_part.to_owned());
}
}
if let Some(params) = req.uri().query() {
let params_iter = params.split('&').take(1);
for val in params_iter {
if let Some(stripped) = val.strip_prefix(ACCESS_TOKEN_KEY) {
return Some(stripped.to_owned());
}
}
}
None
}

4
src/api/web.rs

@ -173,8 +173,8 @@ pub fn static_files(filename: &str) -> Result<(ContentType, &'static [u8]), Erro
"jdenticon.js" => Ok((ContentType::JavaScript, include_bytes!("../static/scripts/jdenticon.js"))), "jdenticon.js" => Ok((ContentType::JavaScript, include_bytes!("../static/scripts/jdenticon.js"))),
"datatables.js" => Ok((ContentType::JavaScript, include_bytes!("../static/scripts/datatables.js"))), "datatables.js" => Ok((ContentType::JavaScript, include_bytes!("../static/scripts/datatables.js"))),
"datatables.css" => Ok((ContentType::CSS, include_bytes!("../static/scripts/datatables.css"))), "datatables.css" => Ok((ContentType::CSS, include_bytes!("../static/scripts/datatables.css"))),
"jquery-3.7.0.slim.js" => { "jquery-3.7.1.slim.js" => {
Ok((ContentType::JavaScript, include_bytes!("../static/scripts/jquery-3.7.0.slim.js"))) Ok((ContentType::JavaScript, include_bytes!("../static/scripts/jquery-3.7.1.slim.js")))
} }
_ => err!(format!("Static file not found: {filename}")), _ => err!(format!("Static file not found: {filename}")),
} }

59
src/auth.rs

@ -2,9 +2,10 @@
// //
use chrono::{Duration, Utc}; use chrono::{Duration, Utc};
use num_traits::FromPrimitive; use num_traits::FromPrimitive;
use once_cell::sync::Lazy; use once_cell::sync::{Lazy, OnceCell};
use jsonwebtoken::{self, errors::ErrorKind, Algorithm, DecodingKey, EncodingKey, Header}; use jsonwebtoken::{self, errors::ErrorKind, Algorithm, DecodingKey, EncodingKey, Header};
use openssl::rsa::Rsa;
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
use serde::ser::Serialize; use serde::ser::Serialize;
@ -26,23 +27,45 @@ static JWT_SEND_ISSUER: Lazy<String> = Lazy::new(|| format!("{}|send", CONFIG.do
static JWT_ORG_API_KEY_ISSUER: Lazy<String> = Lazy::new(|| format!("{}|api.organization", CONFIG.domain_origin())); static JWT_ORG_API_KEY_ISSUER: Lazy<String> = Lazy::new(|| format!("{}|api.organization", CONFIG.domain_origin()));
static JWT_FILE_DOWNLOAD_ISSUER: Lazy<String> = Lazy::new(|| format!("{}|file_download", CONFIG.domain_origin())); static JWT_FILE_DOWNLOAD_ISSUER: Lazy<String> = Lazy::new(|| format!("{}|file_download", CONFIG.domain_origin()));
static PRIVATE_RSA_KEY: Lazy<EncodingKey> = Lazy::new(|| { static PRIVATE_RSA_KEY: OnceCell<EncodingKey> = OnceCell::new();
let key = static PUBLIC_RSA_KEY: OnceCell<DecodingKey> = OnceCell::new();
std::fs::read(CONFIG.private_rsa_key()).unwrap_or_else(|e| panic!("Error loading private RSA Key. \n{e}"));
EncodingKey::from_rsa_pem(&key).unwrap_or_else(|e| panic!("Error decoding private RSA Key.\n{e}"))
});
static PUBLIC_RSA_KEY: Lazy<DecodingKey> = Lazy::new(|| {
let key = std::fs::read(CONFIG.public_rsa_key()).unwrap_or_else(|e| panic!("Error loading public RSA Key. \n{e}"));
DecodingKey::from_rsa_pem(&key).unwrap_or_else(|e| panic!("Error decoding public RSA Key.\n{e}"))
});
pub fn load_keys() { pub fn initialize_keys() -> Result<(), crate::error::Error> {
Lazy::force(&PRIVATE_RSA_KEY); let mut priv_key_buffer = Vec::with_capacity(2048);
Lazy::force(&PUBLIC_RSA_KEY);
let priv_key = {
let mut priv_key_file = File::options().create(true).read(true).write(true).open(CONFIG.private_rsa_key())?;
#[allow(clippy::verbose_file_reads)]
let bytes_read = priv_key_file.read_to_end(&mut priv_key_buffer)?;
if bytes_read > 0 {
Rsa::private_key_from_pem(&priv_key_buffer[..bytes_read])?
} else {
// Only create the key if the file doesn't exist or is empty
let rsa_key = openssl::rsa::Rsa::generate(2048)?;
priv_key_buffer = rsa_key.private_key_to_pem()?;
priv_key_file.write_all(&priv_key_buffer)?;
info!("Private key created correctly.");
rsa_key
}
};
let pub_key_buffer = priv_key.public_key_to_pem()?;
let enc = EncodingKey::from_rsa_pem(&priv_key_buffer)?;
let dec: DecodingKey = DecodingKey::from_rsa_pem(&pub_key_buffer)?;
if PRIVATE_RSA_KEY.set(enc).is_err() {
err!("PRIVATE_RSA_KEY must only be initialized once")
}
if PUBLIC_RSA_KEY.set(dec).is_err() {
err!("PUBLIC_RSA_KEY must only be initialized once")
}
Ok(())
} }
pub fn encode_jwt<T: Serialize>(claims: &T) -> String { pub fn encode_jwt<T: Serialize>(claims: &T) -> String {
match jsonwebtoken::encode(&JWT_HEADER, claims, &PRIVATE_RSA_KEY) { match jsonwebtoken::encode(&JWT_HEADER, claims, PRIVATE_RSA_KEY.wait()) {
Ok(token) => token, Ok(token) => token,
Err(e) => panic!("Error encoding jwt {e}"), Err(e) => panic!("Error encoding jwt {e}"),
} }
@ -56,7 +79,7 @@ fn decode_jwt<T: DeserializeOwned>(token: &str, issuer: String) -> Result<T, Err
validation.set_issuer(&[issuer]); validation.set_issuer(&[issuer]);
let token = token.replace(char::is_whitespace, ""); let token = token.replace(char::is_whitespace, "");
match jsonwebtoken::decode(&token, &PUBLIC_RSA_KEY, &validation) { match jsonwebtoken::decode(&token, PUBLIC_RSA_KEY.wait(), &validation) {
Ok(d) => Ok(d.claims), Ok(d) => Ok(d.claims),
Err(err) => match *err.kind() { Err(err) => match *err.kind() {
ErrorKind::InvalidToken => err!("Token is invalid"), ErrorKind::InvalidToken => err!("Token is invalid"),
@ -799,7 +822,11 @@ impl<'r> FromRequest<'r> for OwnerHeaders {
// //
// Client IP address detection // Client IP address detection
// //
use std::net::IpAddr; use std::{
fs::File,
io::{Read, Write},
net::IpAddr,
};
pub struct ClientIp { pub struct ClientIp {
pub ip: IpAddr, pub ip: IpAddr,

23
src/config.rs

@ -39,7 +39,6 @@ macro_rules! make_config {
struct Inner { struct Inner {
rocket_shutdown_handle: Option<rocket::Shutdown>, rocket_shutdown_handle: Option<rocket::Shutdown>,
ws_shutdown_handle: Option<tokio::sync::oneshot::Sender<()>>,
templates: Handlebars<'static>, templates: Handlebars<'static>,
config: ConfigItems, config: ConfigItems,
@ -361,7 +360,7 @@ make_config! {
/// Sends folder /// Sends folder
sends_folder: String, false, auto, |c| format!("{}/{}", c.data_folder, "sends"); sends_folder: String, false, auto, |c| format!("{}/{}", c.data_folder, "sends");
/// Temp folder |> Used for storing temporary file uploads /// Temp folder |> Used for storing temporary file uploads
tmp_folder: String, false, auto, |c| format!("{}/{}", c.data_folder, "tmp"); tmp_folder: String, false, auto, |c| format!("{}/{}", c.data_folder, "tmp");
/// Templates folder /// Templates folder
templates_folder: String, false, auto, |c| format!("{}/{}", c.data_folder, "templates"); templates_folder: String, false, auto, |c| format!("{}/{}", c.data_folder, "templates");
/// Session JWT key /// Session JWT key
@ -371,11 +370,7 @@ make_config! {
}, },
ws { ws {
/// Enable websocket notifications /// Enable websocket notifications
websocket_enabled: bool, false, def, false; enable_websocket: bool, false, def, true;
/// Websocket address
websocket_address: String, false, def, "0.0.0.0".to_string();
/// Websocket port
websocket_port: u16, false, def, 3012;
}, },
push { push {
/// Enable push notifications /// Enable push notifications
@ -1071,7 +1066,6 @@ impl Config {
Ok(Config { Ok(Config {
inner: RwLock::new(Inner { inner: RwLock::new(Inner {
rocket_shutdown_handle: None, rocket_shutdown_handle: None,
ws_shutdown_handle: None,
templates: load_templates(&config.templates_folder), templates: load_templates(&config.templates_folder),
config, config,
_env, _env,
@ -1164,7 +1158,7 @@ impl Config {
} }
pub fn delete_user_config(&self) -> Result<(), Error> { pub fn delete_user_config(&self) -> Result<(), Error> {
crate::util::delete_file(&CONFIG_FILE)?; std::fs::remove_file(&*CONFIG_FILE)?;
// Empty user config // Empty user config
let usr = ConfigBuilder::default(); let usr = ConfigBuilder::default();
@ -1189,9 +1183,6 @@ impl Config {
pub fn private_rsa_key(&self) -> String { pub fn private_rsa_key(&self) -> String {
format!("{}.pem", CONFIG.rsa_key_filename()) format!("{}.pem", CONFIG.rsa_key_filename())
} }
pub fn public_rsa_key(&self) -> String {
format!("{}.pub.pem", CONFIG.rsa_key_filename())
}
pub fn mail_enabled(&self) -> bool { pub fn mail_enabled(&self) -> bool {
let inner = &self.inner.read().unwrap().config; let inner = &self.inner.read().unwrap().config;
inner._enable_smtp && (inner.smtp_host.is_some() || inner.use_sendmail) inner._enable_smtp && (inner.smtp_host.is_some() || inner.use_sendmail)
@ -1240,16 +1231,8 @@ impl Config {
self.inner.write().unwrap().rocket_shutdown_handle = Some(handle); self.inner.write().unwrap().rocket_shutdown_handle = Some(handle);
} }
pub fn set_ws_shutdown_handle(&self, handle: tokio::sync::oneshot::Sender<()>) {
self.inner.write().unwrap().ws_shutdown_handle = Some(handle);
}
pub fn shutdown(&self) { pub fn shutdown(&self) {
if let Ok(mut c) = self.inner.write() { if let Ok(mut c) = self.inner.write() {
if let Some(handle) = c.ws_shutdown_handle.take() {
handle.send(()).ok();
}
if let Some(handle) = c.rocket_shutdown_handle.take() { if let Some(handle) = c.rocket_shutdown_handle.take() {
handle.notify(); handle.notify();
} }

2
src/db/models/attachment.rs

@ -103,7 +103,7 @@ impl Attachment {
let file_path = &self.get_file_path(); let file_path = &self.get_file_path();
match crate::util::delete_file(file_path) { match std::fs::remove_file(file_path) {
// Ignore "file not found" errors. This can happen when the // Ignore "file not found" errors. This can happen when the
// upstream caller has already cleaned up the file as part of // upstream caller has already cleaned up the file as part of
// its own error handling. // its own error handling.

2
src/error.rs

@ -52,7 +52,6 @@ use rocket::error::Error as RocketErr;
use serde_json::{Error as SerdeErr, Value}; use serde_json::{Error as SerdeErr, Value};
use std::io::Error as IoErr; use std::io::Error as IoErr;
use std::time::SystemTimeError as TimeErr; use std::time::SystemTimeError as TimeErr;
use tokio_tungstenite::tungstenite::Error as TungstError;
use webauthn_rs::error::WebauthnError as WebauthnErr; use webauthn_rs::error::WebauthnError as WebauthnErr;
use yubico::yubicoerror::YubicoError as YubiErr; use yubico::yubicoerror::YubicoError as YubiErr;
@ -91,7 +90,6 @@ make_error! {
DieselCon(DieselConErr): _has_source, _api_error, DieselCon(DieselConErr): _has_source, _api_error,
Webauthn(WebauthnErr): _has_source, _api_error, Webauthn(WebauthnErr): _has_source, _api_error,
WebSocket(TungstError): _has_source, _api_error,
} }
impl std::fmt::Debug for Error { impl std::fmt::Debug for Error {

37
src/main.rs

@ -52,7 +52,7 @@ mod ratelimit;
mod util; mod util;
use crate::api::purge_auth_requests; use crate::api::purge_auth_requests;
use crate::api::WS_ANONYMOUS_SUBSCRIPTIONS; use crate::api::{WS_ANONYMOUS_SUBSCRIPTIONS, WS_USERS};
pub use config::CONFIG; pub use config::CONFIG;
pub use error::{Error, MapResult}; pub use error::{Error, MapResult};
use rocket::data::{Limits, ToByteUnit}; use rocket::data::{Limits, ToByteUnit};
@ -65,13 +65,17 @@ async fn main() -> Result<(), Error> {
launch_info(); launch_info();
use log::LevelFilter as LF; use log::LevelFilter as LF;
let level = LF::from_str(&CONFIG.log_level()).expect("Valid log level"); let level = LF::from_str(&CONFIG.log_level()).unwrap_or_else(|_| {
let valid_log_levels = LF::iter().map(|lvl| lvl.as_str().to_lowercase()).collect::<Vec<String>>().join(", ");
println!("Log level must be one of the following: {valid_log_levels}");
exit(1);
});
init_logging(level).ok(); init_logging(level).ok();
let extra_debug = matches!(level, LF::Trace | LF::Debug); let extra_debug = matches!(level, LF::Trace | LF::Debug);
check_data_folder().await; check_data_folder().await;
check_rsa_keys().unwrap_or_else(|_| { auth::initialize_keys().unwrap_or_else(|_| {
error!("Error creating keys, exiting..."); error!("Error creating keys, exiting...");
exit(1); exit(1);
}); });
@ -444,31 +448,6 @@ async fn container_data_folder_is_persistent(data_folder: &str) -> bool {
true true
} }
fn check_rsa_keys() -> Result<(), crate::error::Error> {
// If the RSA keys don't exist, try to create them
let priv_path = CONFIG.private_rsa_key();
let pub_path = CONFIG.public_rsa_key();
if !util::file_exists(&priv_path) {
let rsa_key = openssl::rsa::Rsa::generate(2048)?;
let priv_key = rsa_key.private_key_to_pem()?;
crate::util::write_file(&priv_path, &priv_key)?;
info!("Private key created correctly.");
}
if !util::file_exists(&pub_path) {
let rsa_key = openssl::rsa::Rsa::private_key_from_pem(&std::fs::read(&priv_path)?)?;
let pub_key = rsa_key.public_key_to_pem()?;
crate::util::write_file(&pub_path, &pub_key)?;
info!("Public key created correctly.");
}
auth::load_keys();
Ok(())
}
fn check_web_vault() { fn check_web_vault() {
if !CONFIG.web_vault_enabled() { if !CONFIG.web_vault_enabled() {
return; return;
@ -522,7 +501,7 @@ async fn launch_rocket(pool: db::DbPool, extra_debug: bool) -> Result<(), Error>
.register([basepath, "/api"].concat(), api::core_catchers()) .register([basepath, "/api"].concat(), api::core_catchers())
.register([basepath, "/admin"].concat(), api::admin_catchers()) .register([basepath, "/admin"].concat(), api::admin_catchers())
.manage(pool) .manage(pool)
.manage(api::start_notification_server()) .manage(Arc::clone(&WS_USERS))
.manage(Arc::clone(&WS_ANONYMOUS_SUBSCRIPTIONS)) .manage(Arc::clone(&WS_ANONYMOUS_SUBSCRIPTIONS))
.attach(util::AppHeaders()) .attach(util::AppHeaders())
.attach(util::Cors()) .attach(util::Cors())

10
src/static/scripts/bootstrap.bundle.js

@ -1,5 +1,5 @@
/*! /*!
* Bootstrap v5.3.1 (https://getbootstrap.com/) * Bootstrap v5.3.2 (https://getbootstrap.com/)
* Copyright 2011-2023 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) * Copyright 2011-2023 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/ */
@ -648,7 +648,7 @@
* Constants * Constants
*/ */
const VERSION = '5.3.1'; const VERSION = '5.3.2';
/** /**
* Class definition * Class definition
@ -729,9 +729,9 @@
if (hrefAttribute.includes('#') && !hrefAttribute.startsWith('#')) { if (hrefAttribute.includes('#') && !hrefAttribute.startsWith('#')) {
hrefAttribute = `#${hrefAttribute.split('#')[1]}`; hrefAttribute = `#${hrefAttribute.split('#')[1]}`;
} }
selector = hrefAttribute && hrefAttribute !== '#' ? hrefAttribute.trim() : null; selector = hrefAttribute && hrefAttribute !== '#' ? parseSelector(hrefAttribute.trim()) : null;
} }
return parseSelector(selector); return selector;
}; };
const SelectorEngine = { const SelectorEngine = {
find(selector, element = document.documentElement) { find(selector, element = document.documentElement) {
@ -5866,7 +5866,7 @@
const CLASS_DROPDOWN = 'dropdown'; const CLASS_DROPDOWN = 'dropdown';
const SELECTOR_DROPDOWN_TOGGLE = '.dropdown-toggle'; const SELECTOR_DROPDOWN_TOGGLE = '.dropdown-toggle';
const SELECTOR_DROPDOWN_MENU = '.dropdown-menu'; const SELECTOR_DROPDOWN_MENU = '.dropdown-menu';
const NOT_SELECTOR_DROPDOWN_TOGGLE = ':not(.dropdown-toggle)'; const NOT_SELECTOR_DROPDOWN_TOGGLE = `:not(${SELECTOR_DROPDOWN_TOGGLE})`;
const SELECTOR_TAB_PANEL = '.list-group, .nav, [role="tablist"]'; const SELECTOR_TAB_PANEL = '.list-group, .nav, [role="tablist"]';
const SELECTOR_OUTER = '.nav-item, .list-group-item'; const SELECTOR_OUTER = '.nav-item, .list-group-item';
const SELECTOR_INNER = `.nav-link${NOT_SELECTOR_DROPDOWN_TOGGLE}, .list-group-item${NOT_SELECTOR_DROPDOWN_TOGGLE}, [role="tab"]${NOT_SELECTOR_DROPDOWN_TOGGLE}`; const SELECTOR_INNER = `.nav-link${NOT_SELECTOR_DROPDOWN_TOGGLE}, .list-group-item${NOT_SELECTOR_DROPDOWN_TOGGLE}, [role="tab"]${NOT_SELECTOR_DROPDOWN_TOGGLE}`;

81
src/static/scripts/bootstrap.css

@ -1,6 +1,6 @@
@charset "UTF-8"; @charset "UTF-8";
/*! /*!
* Bootstrap v5.3.1 (https://getbootstrap.com/) * Bootstrap v5.3.2 (https://getbootstrap.com/)
* Copyright 2011-2023 The Bootstrap Authors * Copyright 2011-2023 The Bootstrap Authors
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/ */
@ -99,6 +99,7 @@
--bs-link-hover-color: #0a58ca; --bs-link-hover-color: #0a58ca;
--bs-link-hover-color-rgb: 10, 88, 202; --bs-link-hover-color-rgb: 10, 88, 202;
--bs-code-color: #d63384; --bs-code-color: #d63384;
--bs-highlight-color: #212529;
--bs-highlight-bg: #fff3cd; --bs-highlight-bg: #fff3cd;
--bs-border-width: 1px; --bs-border-width: 1px;
--bs-border-style: solid; --bs-border-style: solid;
@ -170,6 +171,8 @@
--bs-link-color-rgb: 110, 168, 254; --bs-link-color-rgb: 110, 168, 254;
--bs-link-hover-color-rgb: 139, 185, 254; --bs-link-hover-color-rgb: 139, 185, 254;
--bs-code-color: #e685b5; --bs-code-color: #e685b5;
--bs-highlight-color: #dee2e6;
--bs-highlight-bg: #664d03;
--bs-border-color: #495057; --bs-border-color: #495057;
--bs-border-color-translucent: rgba(255, 255, 255, 0.15); --bs-border-color-translucent: rgba(255, 255, 255, 0.15);
--bs-form-valid-color: #75b798; --bs-form-valid-color: #75b798;
@ -325,6 +328,7 @@ small, .small {
mark, .mark { mark, .mark {
padding: 0.1875em; padding: 0.1875em;
color: var(--bs-highlight-color);
background-color: var(--bs-highlight-bg); background-color: var(--bs-highlight-bg);
} }
@ -819,7 +823,7 @@ progress {
.row-cols-3 > * { .row-cols-3 > * {
flex: 0 0 auto; flex: 0 0 auto;
width: 33.3333333333%; width: 33.33333333%;
} }
.row-cols-4 > * { .row-cols-4 > * {
@ -834,7 +838,7 @@ progress {
.row-cols-6 > * { .row-cols-6 > * {
flex: 0 0 auto; flex: 0 0 auto;
width: 16.6666666667%; width: 16.66666667%;
} }
.col-auto { .col-auto {
@ -1024,7 +1028,7 @@ progress {
} }
.row-cols-sm-3 > * { .row-cols-sm-3 > * {
flex: 0 0 auto; flex: 0 0 auto;
width: 33.3333333333%; width: 33.33333333%;
} }
.row-cols-sm-4 > * { .row-cols-sm-4 > * {
flex: 0 0 auto; flex: 0 0 auto;
@ -1036,7 +1040,7 @@ progress {
} }
.row-cols-sm-6 > * { .row-cols-sm-6 > * {
flex: 0 0 auto; flex: 0 0 auto;
width: 16.6666666667%; width: 16.66666667%;
} }
.col-sm-auto { .col-sm-auto {
flex: 0 0 auto; flex: 0 0 auto;
@ -1193,7 +1197,7 @@ progress {
} }
.row-cols-md-3 > * { .row-cols-md-3 > * {
flex: 0 0 auto; flex: 0 0 auto;
width: 33.3333333333%; width: 33.33333333%;
} }
.row-cols-md-4 > * { .row-cols-md-4 > * {
flex: 0 0 auto; flex: 0 0 auto;
@ -1205,7 +1209,7 @@ progress {
} }
.row-cols-md-6 > * { .row-cols-md-6 > * {
flex: 0 0 auto; flex: 0 0 auto;
width: 16.6666666667%; width: 16.66666667%;
} }
.col-md-auto { .col-md-auto {
flex: 0 0 auto; flex: 0 0 auto;
@ -1362,7 +1366,7 @@ progress {
} }
.row-cols-lg-3 > * { .row-cols-lg-3 > * {
flex: 0 0 auto; flex: 0 0 auto;
width: 33.3333333333%; width: 33.33333333%;
} }
.row-cols-lg-4 > * { .row-cols-lg-4 > * {
flex: 0 0 auto; flex: 0 0 auto;
@ -1374,7 +1378,7 @@ progress {
} }
.row-cols-lg-6 > * { .row-cols-lg-6 > * {
flex: 0 0 auto; flex: 0 0 auto;
width: 16.6666666667%; width: 16.66666667%;
} }
.col-lg-auto { .col-lg-auto {
flex: 0 0 auto; flex: 0 0 auto;
@ -1531,7 +1535,7 @@ progress {
} }
.row-cols-xl-3 > * { .row-cols-xl-3 > * {
flex: 0 0 auto; flex: 0 0 auto;
width: 33.3333333333%; width: 33.33333333%;
} }
.row-cols-xl-4 > * { .row-cols-xl-4 > * {
flex: 0 0 auto; flex: 0 0 auto;
@ -1543,7 +1547,7 @@ progress {
} }
.row-cols-xl-6 > * { .row-cols-xl-6 > * {
flex: 0 0 auto; flex: 0 0 auto;
width: 16.6666666667%; width: 16.66666667%;
} }
.col-xl-auto { .col-xl-auto {
flex: 0 0 auto; flex: 0 0 auto;
@ -1700,7 +1704,7 @@ progress {
} }
.row-cols-xxl-3 > * { .row-cols-xxl-3 > * {
flex: 0 0 auto; flex: 0 0 auto;
width: 33.3333333333%; width: 33.33333333%;
} }
.row-cols-xxl-4 > * { .row-cols-xxl-4 > * {
flex: 0 0 auto; flex: 0 0 auto;
@ -1712,7 +1716,7 @@ progress {
} }
.row-cols-xxl-6 > * { .row-cols-xxl-6 > * {
flex: 0 0 auto; flex: 0 0 auto;
width: 16.6666666667%; width: 16.66666667%;
} }
.col-xxl-auto { .col-xxl-auto {
flex: 0 0 auto; flex: 0 0 auto;
@ -1856,16 +1860,16 @@ progress {
--bs-table-bg-type: initial; --bs-table-bg-type: initial;
--bs-table-color-state: initial; --bs-table-color-state: initial;
--bs-table-bg-state: initial; --bs-table-bg-state: initial;
--bs-table-color: var(--bs-body-color); --bs-table-color: var(--bs-emphasis-color);
--bs-table-bg: var(--bs-body-bg); --bs-table-bg: var(--bs-body-bg);
--bs-table-border-color: var(--bs-border-color); --bs-table-border-color: var(--bs-border-color);
--bs-table-accent-bg: transparent; --bs-table-accent-bg: transparent;
--bs-table-striped-color: var(--bs-body-color); --bs-table-striped-color: var(--bs-emphasis-color);
--bs-table-striped-bg: rgba(0, 0, 0, 0.05); --bs-table-striped-bg: rgba(var(--bs-emphasis-color-rgb), 0.05);
--bs-table-active-color: var(--bs-body-color); --bs-table-active-color: var(--bs-emphasis-color);
--bs-table-active-bg: rgba(0, 0, 0, 0.1); --bs-table-active-bg: rgba(var(--bs-emphasis-color-rgb), 0.1);
--bs-table-hover-color: var(--bs-body-color); --bs-table-hover-color: var(--bs-emphasis-color);
--bs-table-hover-bg: rgba(0, 0, 0, 0.075); --bs-table-hover-bg: rgba(var(--bs-emphasis-color-rgb), 0.075);
width: 100%; width: 100%;
margin-bottom: 1rem; margin-bottom: 1rem;
vertical-align: top; vertical-align: top;
@ -1934,7 +1938,7 @@ progress {
.table-primary { .table-primary {
--bs-table-color: #000; --bs-table-color: #000;
--bs-table-bg: #cfe2ff; --bs-table-bg: #cfe2ff;
--bs-table-border-color: #bacbe6; --bs-table-border-color: #a6b5cc;
--bs-table-striped-bg: #c5d7f2; --bs-table-striped-bg: #c5d7f2;
--bs-table-striped-color: #000; --bs-table-striped-color: #000;
--bs-table-active-bg: #bacbe6; --bs-table-active-bg: #bacbe6;
@ -1948,7 +1952,7 @@ progress {
.table-secondary { .table-secondary {
--bs-table-color: #000; --bs-table-color: #000;
--bs-table-bg: #e2e3e5; --bs-table-bg: #e2e3e5;
--bs-table-border-color: #cbccce; --bs-table-border-color: #b5b6b7;
--bs-table-striped-bg: #d7d8da; --bs-table-striped-bg: #d7d8da;
--bs-table-striped-color: #000; --bs-table-striped-color: #000;
--bs-table-active-bg: #cbccce; --bs-table-active-bg: #cbccce;
@ -1962,7 +1966,7 @@ progress {
.table-success { .table-success {
--bs-table-color: #000; --bs-table-color: #000;
--bs-table-bg: #d1e7dd; --bs-table-bg: #d1e7dd;
--bs-table-border-color: #bcd0c7; --bs-table-border-color: #a7b9b1;
--bs-table-striped-bg: #c7dbd2; --bs-table-striped-bg: #c7dbd2;
--bs-table-striped-color: #000; --bs-table-striped-color: #000;
--bs-table-active-bg: #bcd0c7; --bs-table-active-bg: #bcd0c7;
@ -1976,7 +1980,7 @@ progress {
.table-info { .table-info {
--bs-table-color: #000; --bs-table-color: #000;
--bs-table-bg: #cff4fc; --bs-table-bg: #cff4fc;
--bs-table-border-color: #badce3; --bs-table-border-color: #a6c3ca;
--bs-table-striped-bg: #c5e8ef; --bs-table-striped-bg: #c5e8ef;
--bs-table-striped-color: #000; --bs-table-striped-color: #000;
--bs-table-active-bg: #badce3; --bs-table-active-bg: #badce3;
@ -1990,7 +1994,7 @@ progress {
.table-warning { .table-warning {
--bs-table-color: #000; --bs-table-color: #000;
--bs-table-bg: #fff3cd; --bs-table-bg: #fff3cd;
--bs-table-border-color: #e6dbb9; --bs-table-border-color: #ccc2a4;
--bs-table-striped-bg: #f2e7c3; --bs-table-striped-bg: #f2e7c3;
--bs-table-striped-color: #000; --bs-table-striped-color: #000;
--bs-table-active-bg: #e6dbb9; --bs-table-active-bg: #e6dbb9;
@ -2004,7 +2008,7 @@ progress {
.table-danger { .table-danger {
--bs-table-color: #000; --bs-table-color: #000;
--bs-table-bg: #f8d7da; --bs-table-bg: #f8d7da;
--bs-table-border-color: #dfc2c4; --bs-table-border-color: #c6acae;
--bs-table-striped-bg: #eccccf; --bs-table-striped-bg: #eccccf;
--bs-table-striped-color: #000; --bs-table-striped-color: #000;
--bs-table-active-bg: #dfc2c4; --bs-table-active-bg: #dfc2c4;
@ -2018,7 +2022,7 @@ progress {
.table-light { .table-light {
--bs-table-color: #000; --bs-table-color: #000;
--bs-table-bg: #f8f9fa; --bs-table-bg: #f8f9fa;
--bs-table-border-color: #dfe0e1; --bs-table-border-color: #c6c7c8;
--bs-table-striped-bg: #ecedee; --bs-table-striped-bg: #ecedee;
--bs-table-striped-color: #000; --bs-table-striped-color: #000;
--bs-table-active-bg: #dfe0e1; --bs-table-active-bg: #dfe0e1;
@ -2032,7 +2036,7 @@ progress {
.table-dark { .table-dark {
--bs-table-color: #fff; --bs-table-color: #fff;
--bs-table-bg: #212529; --bs-table-bg: #212529;
--bs-table-border-color: #373b3e; --bs-table-border-color: #4d5154;
--bs-table-striped-bg: #2c3034; --bs-table-striped-bg: #2c3034;
--bs-table-striped-color: #fff; --bs-table-striped-color: #fff;
--bs-table-active-bg: #373b3e; --bs-table-active-bg: #373b3e;
@ -2388,6 +2392,7 @@ textarea.form-control-lg {
.form-check-input { .form-check-input {
--bs-form-check-bg: var(--bs-body-bg); --bs-form-check-bg: var(--bs-body-bg);
flex-shrink: 0;
width: 1em; width: 1em;
height: 1em; height: 1em;
margin-top: 0.25em; margin-top: 0.25em;
@ -2544,7 +2549,7 @@ textarea.form-control-lg {
height: 0.5rem; height: 0.5rem;
color: transparent; color: transparent;
cursor: pointer; cursor: pointer;
background-color: var(--bs-tertiary-bg); background-color: var(--bs-secondary-bg);
border-color: transparent; border-color: transparent;
border-radius: 1rem; border-radius: 1rem;
} }
@ -2573,7 +2578,7 @@ textarea.form-control-lg {
height: 0.5rem; height: 0.5rem;
color: transparent; color: transparent;
cursor: pointer; cursor: pointer;
background-color: var(--bs-tertiary-bg); background-color: var(--bs-secondary-bg);
border-color: transparent; border-color: transparent;
border-radius: 1rem; border-radius: 1rem;
} }
@ -3431,7 +3436,7 @@ textarea.form-control-lg {
--bs-dropdown-inner-border-radius: calc(var(--bs-border-radius) - var(--bs-border-width)); --bs-dropdown-inner-border-radius: calc(var(--bs-border-radius) - var(--bs-border-width));
--bs-dropdown-divider-bg: var(--bs-border-color-translucent); --bs-dropdown-divider-bg: var(--bs-border-color-translucent);
--bs-dropdown-divider-margin-y: 0.5rem; --bs-dropdown-divider-margin-y: 0.5rem;
--bs-dropdown-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15); --bs-dropdown-box-shadow: var(--bs-box-shadow);
--bs-dropdown-link-color: var(--bs-body-color); --bs-dropdown-link-color: var(--bs-body-color);
--bs-dropdown-link-hover-color: var(--bs-body-color); --bs-dropdown-link-hover-color: var(--bs-body-color);
--bs-dropdown-link-hover-bg: var(--bs-tertiary-bg); --bs-dropdown-link-hover-bg: var(--bs-tertiary-bg);
@ -5473,7 +5478,7 @@ textarea.form-control-lg {
--bs-modal-border-color: var(--bs-border-color-translucent); --bs-modal-border-color: var(--bs-border-color-translucent);
--bs-modal-border-width: var(--bs-border-width); --bs-modal-border-width: var(--bs-border-width);
--bs-modal-border-radius: var(--bs-border-radius-lg); --bs-modal-border-radius: var(--bs-border-radius-lg);
--bs-modal-box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075); --bs-modal-box-shadow: var(--bs-box-shadow-sm);
--bs-modal-inner-border-radius: calc(var(--bs-border-radius-lg) - (var(--bs-border-width))); --bs-modal-inner-border-radius: calc(var(--bs-border-radius-lg) - (var(--bs-border-width)));
--bs-modal-header-padding-x: 1rem; --bs-modal-header-padding-x: 1rem;
--bs-modal-header-padding-y: 1rem; --bs-modal-header-padding-y: 1rem;
@ -5614,7 +5619,7 @@ textarea.form-control-lg {
@media (min-width: 576px) { @media (min-width: 576px) {
.modal { .modal {
--bs-modal-margin: 1.75rem; --bs-modal-margin: 1.75rem;
--bs-modal-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15); --bs-modal-box-shadow: var(--bs-box-shadow);
} }
.modal-dialog { .modal-dialog {
max-width: var(--bs-modal-width); max-width: var(--bs-modal-width);
@ -5866,7 +5871,7 @@ textarea.form-control-lg {
--bs-popover-border-color: var(--bs-border-color-translucent); --bs-popover-border-color: var(--bs-border-color-translucent);
--bs-popover-border-radius: var(--bs-border-radius-lg); --bs-popover-border-radius: var(--bs-border-radius-lg);
--bs-popover-inner-border-radius: calc(var(--bs-border-radius-lg) - var(--bs-border-width)); --bs-popover-inner-border-radius: calc(var(--bs-border-radius-lg) - var(--bs-border-width));
--bs-popover-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15); --bs-popover-box-shadow: var(--bs-box-shadow);
--bs-popover-header-padding-x: 1rem; --bs-popover-header-padding-x: 1rem;
--bs-popover-header-padding-y: 0.5rem; --bs-popover-header-padding-y: 0.5rem;
--bs-popover-header-font-size: 1rem; --bs-popover-header-font-size: 1rem;
@ -6301,7 +6306,7 @@ textarea.form-control-lg {
--bs-offcanvas-bg: var(--bs-body-bg); --bs-offcanvas-bg: var(--bs-body-bg);
--bs-offcanvas-border-width: var(--bs-border-width); --bs-offcanvas-border-width: var(--bs-border-width);
--bs-offcanvas-border-color: var(--bs-border-color-translucent); --bs-offcanvas-border-color: var(--bs-border-color-translucent);
--bs-offcanvas-box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075); --bs-offcanvas-box-shadow: var(--bs-box-shadow-sm);
--bs-offcanvas-transition: transform 0.3s ease-in-out; --bs-offcanvas-transition: transform 0.3s ease-in-out;
--bs-offcanvas-title-line-height: 1.5; --bs-offcanvas-title-line-height: 1.5;
} }
@ -7380,15 +7385,15 @@ textarea.form-control-lg {
} }
.shadow { .shadow {
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15) !important; box-shadow: var(--bs-box-shadow) !important;
} }
.shadow-sm { .shadow-sm {
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075) !important; box-shadow: var(--bs-box-shadow-sm) !important;
} }
.shadow-lg { .shadow-lg {
box-shadow: 0 1rem 3rem rgba(0, 0, 0, 0.175) !important; box-shadow: var(--bs-box-shadow-lg) !important;
} }
.shadow-none { .shadow-none {

350
src/static/scripts/datatables.css

@ -4,10 +4,10 @@
* *
* To rebuild or modify this file with the latest versions of the included * To rebuild or modify this file with the latest versions of the included
* software please visit: * software please visit:
* https://datatables.net/download/#bs5/dt-1.13.6 * https://datatables.net/download/#bs5/dt-2.0.0
* *
* Included libraries: * Included libraries:
* DataTables 1.13.6 * DataTables 2.0.0
*/ */
@charset "UTF-8"; @charset "UTF-8";
@ -30,76 +30,124 @@ table.dataTable td.dt-control {
} }
table.dataTable td.dt-control:before { table.dataTable td.dt-control:before {
display: inline-block; display: inline-block;
color: rgba(0, 0, 0, 0.5); box-sizing: border-box;
content: "►"; content: "";
border-top: 5px solid transparent;
border-left: 10px solid rgba(0, 0, 0, 0.5);
border-bottom: 5px solid transparent;
border-right: 0px solid transparent;
} }
table.dataTable tr.dt-hasChild td.dt-control:before { table.dataTable tr.dt-hasChild td.dt-control:before {
content: "▼"; border-top: 10px solid rgba(0, 0, 0, 0.5);
border-left: 5px solid transparent;
border-bottom: 0px solid transparent;
border-right: 5px solid transparent;
} }
html.dark table.dataTable td.dt-control:before { html.dark table.dataTable td.dt-control:before,
color: rgba(255, 255, 255, 0.5); :root[data-bs-theme=dark] table.dataTable td.dt-control:before {
border-left-color: rgba(255, 255, 255, 0.5);
} }
html.dark table.dataTable tr.dt-hasChild td.dt-control:before { html.dark table.dataTable tr.dt-hasChild td.dt-control:before,
color: rgba(255, 255, 255, 0.5); :root[data-bs-theme=dark] table.dataTable tr.dt-hasChild td.dt-control:before {
border-top-color: rgba(255, 255, 255, 0.5);
border-left-color: transparent;
} }
table.dataTable thead > tr > th.sorting, table.dataTable thead > tr > th.sorting_asc, table.dataTable thead > tr > th.sorting_desc, table.dataTable thead > tr > th.sorting_asc_disabled, table.dataTable thead > tr > th.sorting_desc_disabled, div.dt-scroll-body thead tr,
table.dataTable thead > tr > td.sorting, div.dt-scroll-body tfoot tr {
table.dataTable thead > tr > td.sorting_asc, height: 0;
table.dataTable thead > tr > td.sorting_desc, }
table.dataTable thead > tr > td.sorting_asc_disabled, div.dt-scroll-body thead tr th, div.dt-scroll-body thead tr td,
table.dataTable thead > tr > td.sorting_desc_disabled { div.dt-scroll-body tfoot tr th,
cursor: pointer; div.dt-scroll-body tfoot tr td {
position: relative; height: 0 !important;
padding-right: 26px; padding-top: 0px !important;
} padding-bottom: 0px !important;
table.dataTable thead > tr > th.sorting:before, table.dataTable thead > tr > th.sorting:after, table.dataTable thead > tr > th.sorting_asc:before, table.dataTable thead > tr > th.sorting_asc:after, table.dataTable thead > tr > th.sorting_desc:before, table.dataTable thead > tr > th.sorting_desc:after, table.dataTable thead > tr > th.sorting_asc_disabled:before, table.dataTable thead > tr > th.sorting_asc_disabled:after, table.dataTable thead > tr > th.sorting_desc_disabled:before, table.dataTable thead > tr > th.sorting_desc_disabled:after, border-top-width: 0px !important;
table.dataTable thead > tr > td.sorting:before, border-bottom-width: 0px !important;
table.dataTable thead > tr > td.sorting:after, }
table.dataTable thead > tr > td.sorting_asc:before, div.dt-scroll-body thead tr th div.dt-scroll-sizing, div.dt-scroll-body thead tr td div.dt-scroll-sizing,
table.dataTable thead > tr > td.sorting_asc:after, div.dt-scroll-body tfoot tr th div.dt-scroll-sizing,
table.dataTable thead > tr > td.sorting_desc:before, div.dt-scroll-body tfoot tr td div.dt-scroll-sizing {
table.dataTable thead > tr > td.sorting_desc:after, height: 0 !important;
table.dataTable thead > tr > td.sorting_asc_disabled:before, overflow: hidden !important;
table.dataTable thead > tr > td.sorting_asc_disabled:after, }
table.dataTable thead > tr > td.sorting_desc_disabled:before,
table.dataTable thead > tr > td.sorting_desc_disabled:after { table.dataTable thead > tr > th:active,
table.dataTable thead > tr > td:active {
outline: none;
}
table.dataTable thead > tr > th.dt-orderable-asc span.dt-column-order:before, table.dataTable thead > tr > th.dt-ordering-asc span.dt-column-order:before,
table.dataTable thead > tr > td.dt-orderable-asc span.dt-column-order:before,
table.dataTable thead > tr > td.dt-ordering-asc span.dt-column-order:before {
position: absolute; position: absolute;
display: block; display: block;
opacity: 0.125;
right: 10px;
line-height: 9px;
font-size: 0.8em;
}
table.dataTable thead > tr > th.sorting:before, table.dataTable thead > tr > th.sorting_asc:before, table.dataTable thead > tr > th.sorting_desc:before, table.dataTable thead > tr > th.sorting_asc_disabled:before, table.dataTable thead > tr > th.sorting_desc_disabled:before,
table.dataTable thead > tr > td.sorting:before,
table.dataTable thead > tr > td.sorting_asc:before,
table.dataTable thead > tr > td.sorting_desc:before,
table.dataTable thead > tr > td.sorting_asc_disabled:before,
table.dataTable thead > tr > td.sorting_desc_disabled:before {
bottom: 50%; bottom: 50%;
content: "▲"; content: "▲";
content: "▲"/""; content: "▲"/"";
} }
table.dataTable thead > tr > th.sorting:after, table.dataTable thead > tr > th.sorting_asc:after, table.dataTable thead > tr > th.sorting_desc:after, table.dataTable thead > tr > th.sorting_asc_disabled:after, table.dataTable thead > tr > th.sorting_desc_disabled:after, table.dataTable thead > tr > th.dt-orderable-desc span.dt-column-order:after, table.dataTable thead > tr > th.dt-ordering-desc span.dt-column-order:after,
table.dataTable thead > tr > td.sorting:after, table.dataTable thead > tr > td.dt-orderable-desc span.dt-column-order:after,
table.dataTable thead > tr > td.sorting_asc:after, table.dataTable thead > tr > td.dt-ordering-desc span.dt-column-order:after {
table.dataTable thead > tr > td.sorting_desc:after, position: absolute;
table.dataTable thead > tr > td.sorting_asc_disabled:after, display: block;
table.dataTable thead > tr > td.sorting_desc_disabled:after {
top: 50%; top: 50%;
content: "▼"; content: "▼";
content: "▼"/""; content: "▼"/"";
} }
table.dataTable thead > tr > th.sorting_asc:before, table.dataTable thead > tr > th.sorting_desc:after, table.dataTable thead > tr > th.dt-orderable-asc, table.dataTable thead > tr > th.dt-orderable-desc, table.dataTable thead > tr > th.dt-ordering-asc, table.dataTable thead > tr > th.dt-ordering-desc,
table.dataTable thead > tr > td.sorting_asc:before, table.dataTable thead > tr > td.dt-orderable-asc,
table.dataTable thead > tr > td.sorting_desc:after { table.dataTable thead > tr > td.dt-orderable-desc,
table.dataTable thead > tr > td.dt-ordering-asc,
table.dataTable thead > tr > td.dt-ordering-desc {
position: relative;
padding-right: 30px;
}
table.dataTable thead > tr > th.dt-orderable-asc span.dt-column-order, table.dataTable thead > tr > th.dt-orderable-desc span.dt-column-order, table.dataTable thead > tr > th.dt-ordering-asc span.dt-column-order, table.dataTable thead > tr > th.dt-ordering-desc span.dt-column-order,
table.dataTable thead > tr > td.dt-orderable-asc span.dt-column-order,
table.dataTable thead > tr > td.dt-orderable-desc span.dt-column-order,
table.dataTable thead > tr > td.dt-ordering-asc span.dt-column-order,
table.dataTable thead > tr > td.dt-ordering-desc span.dt-column-order {
position: absolute;
right: 12px;
top: 0;
bottom: 0;
width: 12px;
}
table.dataTable thead > tr > th.dt-orderable-asc span.dt-column-order:before, table.dataTable thead > tr > th.dt-orderable-asc span.dt-column-order:after, table.dataTable thead > tr > th.dt-orderable-desc span.dt-column-order:before, table.dataTable thead > tr > th.dt-orderable-desc span.dt-column-order:after, table.dataTable thead > tr > th.dt-ordering-asc span.dt-column-order:before, table.dataTable thead > tr > th.dt-ordering-asc span.dt-column-order:after, table.dataTable thead > tr > th.dt-ordering-desc span.dt-column-order:before, table.dataTable thead > tr > th.dt-ordering-desc span.dt-column-order:after,
table.dataTable thead > tr > td.dt-orderable-asc span.dt-column-order:before,
table.dataTable thead > tr > td.dt-orderable-asc span.dt-column-order:after,
table.dataTable thead > tr > td.dt-orderable-desc span.dt-column-order:before,
table.dataTable thead > tr > td.dt-orderable-desc span.dt-column-order:after,
table.dataTable thead > tr > td.dt-ordering-asc span.dt-column-order:before,
table.dataTable thead > tr > td.dt-ordering-asc span.dt-column-order:after,
table.dataTable thead > tr > td.dt-ordering-desc span.dt-column-order:before,
table.dataTable thead > tr > td.dt-ordering-desc span.dt-column-order:after {
left: 0;
opacity: 0.125;
line-height: 9px;
font-size: 0.8em;
}
table.dataTable thead > tr > th.dt-orderable-asc, table.dataTable thead > tr > th.dt-orderable-desc,
table.dataTable thead > tr > td.dt-orderable-asc,
table.dataTable thead > tr > td.dt-orderable-desc {
cursor: pointer;
}
table.dataTable thead > tr > th.dt-orderable-asc:hover, table.dataTable thead > tr > th.dt-orderable-desc:hover,
table.dataTable thead > tr > td.dt-orderable-asc:hover,
table.dataTable thead > tr > td.dt-orderable-desc:hover {
outline: 2px solid rgba(0, 0, 0, 0.05);
outline-offset: -2px;
}
table.dataTable thead > tr > th.dt-ordering-asc span.dt-column-order:before, table.dataTable thead > tr > th.dt-ordering-desc span.dt-column-order:after,
table.dataTable thead > tr > td.dt-ordering-asc span.dt-column-order:before,
table.dataTable thead > tr > td.dt-ordering-desc span.dt-column-order:after {
opacity: 0.6; opacity: 0.6;
} }
table.dataTable thead > tr > th.sorting_desc_disabled:after, table.dataTable thead > tr > th.sorting_asc_disabled:before, table.dataTable thead > tr > th.sorting_desc_disabled span.dt-column-order:after, table.dataTable thead > tr > th.sorting_asc_disabled span.dt-column-order:before,
table.dataTable thead > tr > td.sorting_desc_disabled:after, table.dataTable thead > tr > td.sorting_desc_disabled span.dt-column-order:after,
table.dataTable thead > tr > td.sorting_asc_disabled:before { table.dataTable thead > tr > td.sorting_asc_disabled span.dt-column-order:before {
display: none; display: none;
} }
table.dataTable thead > tr > th:active, table.dataTable thead > tr > th:active,
@ -107,29 +155,39 @@ table.dataTable thead > tr > td:active {
outline: none; outline: none;
} }
div.dataTables_scrollBody > table.dataTable > thead > tr > th:before, div.dataTables_scrollBody > table.dataTable > thead > tr > th:after, div.dt-scroll-body > table.dataTable > thead > tr > th,
div.dataTables_scrollBody > table.dataTable > thead > tr > td:before, div.dt-scroll-body > table.dataTable > thead > tr > td {
div.dataTables_scrollBody > table.dataTable > thead > tr > td:after { overflow: hidden;
display: none; }
:root.dark table.dataTable thead > tr > th.dt-orderable-asc:hover, :root.dark table.dataTable thead > tr > th.dt-orderable-desc:hover,
:root.dark table.dataTable thead > tr > td.dt-orderable-asc:hover,
:root.dark table.dataTable thead > tr > td.dt-orderable-desc:hover,
:root[data-bs-theme=dark] table.dataTable thead > tr > th.dt-orderable-asc:hover,
:root[data-bs-theme=dark] table.dataTable thead > tr > th.dt-orderable-desc:hover,
:root[data-bs-theme=dark] table.dataTable thead > tr > td.dt-orderable-asc:hover,
:root[data-bs-theme=dark] table.dataTable thead > tr > td.dt-orderable-desc:hover {
outline: 2px solid rgba(255, 255, 255, 0.05);
} }
div.dataTables_processing { div.dt-processing {
position: absolute; position: absolute;
top: 50%; top: 50%;
left: 50%; left: 50%;
width: 200px; width: 200px;
margin-left: -100px; margin-left: -100px;
margin-top: -26px; margin-top: -22px;
text-align: center; text-align: center;
padding: 2px; padding: 2px;
z-index: 10;
} }
div.dataTables_processing > div:last-child { div.dt-processing > div:last-child {
position: relative; position: relative;
width: 80px; width: 80px;
height: 15px; height: 15px;
margin: 1em auto; margin: 1em auto;
} }
div.dataTables_processing > div:last-child > div { div.dt-processing > div:last-child > div {
position: absolute; position: absolute;
top: 0; top: 0;
width: 13px; width: 13px;
@ -139,19 +197,19 @@ div.dataTables_processing > div:last-child > div {
background: rgb(var(--dt-row-selected)); background: rgb(var(--dt-row-selected));
animation-timing-function: cubic-bezier(0, 1, 1, 0); animation-timing-function: cubic-bezier(0, 1, 1, 0);
} }
div.dataTables_processing > div:last-child > div:nth-child(1) { div.dt-processing > div:last-child > div:nth-child(1) {
left: 8px; left: 8px;
animation: datatables-loader-1 0.6s infinite; animation: datatables-loader-1 0.6s infinite;
} }
div.dataTables_processing > div:last-child > div:nth-child(2) { div.dt-processing > div:last-child > div:nth-child(2) {
left: 8px; left: 8px;
animation: datatables-loader-2 0.6s infinite; animation: datatables-loader-2 0.6s infinite;
} }
div.dataTables_processing > div:last-child > div:nth-child(3) { div.dt-processing > div:last-child > div:nth-child(3) {
left: 32px; left: 32px;
animation: datatables-loader-2 0.6s infinite; animation: datatables-loader-2 0.6s infinite;
} }
div.dataTables_processing > div:last-child > div:nth-child(4) { div.dt-processing > div:last-child > div:nth-child(4) {
left: 56px; left: 56px;
animation: datatables-loader-3 0.6s infinite; animation: datatables-loader-3 0.6s infinite;
} }
@ -183,13 +241,16 @@ div.dataTables_processing > div:last-child > div:nth-child(4) {
table.dataTable.nowrap th, table.dataTable.nowrap td { table.dataTable.nowrap th, table.dataTable.nowrap td {
white-space: nowrap; white-space: nowrap;
} }
table.dataTable th,
table.dataTable td {
box-sizing: border-box;
}
table.dataTable th.dt-left, table.dataTable th.dt-left,
table.dataTable td.dt-left { table.dataTable td.dt-left {
text-align: left; text-align: left;
} }
table.dataTable th.dt-center, table.dataTable th.dt-center,
table.dataTable td.dt-center, table.dataTable td.dt-center {
table.dataTable td.dataTables_empty {
text-align: center; text-align: center;
} }
table.dataTable th.dt-right, table.dataTable th.dt-right,
@ -204,6 +265,16 @@ table.dataTable th.dt-nowrap,
table.dataTable td.dt-nowrap { table.dataTable td.dt-nowrap {
white-space: nowrap; white-space: nowrap;
} }
table.dataTable th.dt-empty,
table.dataTable td.dt-empty {
text-align: center;
vertical-align: top;
}
table.dataTable th.dt-type-numeric, table.dataTable th.dt-type-date,
table.dataTable td.dt-type-numeric,
table.dataTable td.dt-type-date {
text-align: right;
}
table.dataTable thead th, table.dataTable thead th,
table.dataTable thead td, table.dataTable thead td,
table.dataTable tfoot th, table.dataTable tfoot th,
@ -266,179 +337,150 @@ table.dataTable tbody td.dt-body-nowrap {
* ©2020 SpryMedia Ltd, all rights reserved. * ©2020 SpryMedia Ltd, all rights reserved.
* License: MIT datatables.net/license/mit * License: MIT datatables.net/license/mit
*/ */
table.dataTable { table.table.dataTable {
clear: both; clear: both;
margin-top: 6px !important; margin-bottom: 0;
margin-bottom: 6px !important; max-width: none;
max-width: none !important;
border-collapse: separate !important;
border-spacing: 0; border-spacing: 0;
} }
table.dataTable td, table.table.dataTable.table-striped > tbody > tr:nth-of-type(2n+1) > * {
table.dataTable th {
-webkit-box-sizing: content-box;
box-sizing: content-box;
}
table.dataTable td.dataTables_empty,
table.dataTable th.dataTables_empty {
text-align: center;
}
table.dataTable.nowrap th,
table.dataTable.nowrap td {
white-space: nowrap;
}
table.dataTable.table-striped > tbody > tr:nth-of-type(2n+1) > * {
box-shadow: none; box-shadow: none;
} }
table.dataTable > tbody > tr { table.table.dataTable > :not(caption) > * > * {
background-color: transparent; background-color: transparent;
} }
table.dataTable > tbody > tr.selected > * { table.table.dataTable > tbody > tr {
background-color: transparent;
}
table.table.dataTable > tbody > tr.selected > * {
box-shadow: inset 0 0 0 9999px rgb(13, 110, 253); box-shadow: inset 0 0 0 9999px rgb(13, 110, 253);
box-shadow: inset 0 0 0 9999px rgb(var(--dt-row-selected)); box-shadow: inset 0 0 0 9999px rgb(var(--dt-row-selected));
color: rgb(255, 255, 255); color: rgb(255, 255, 255);
color: rgb(var(--dt-row-selected-text)); color: rgb(var(--dt-row-selected-text));
} }
table.dataTable > tbody > tr.selected a { table.table.dataTable > tbody > tr.selected a {
color: rgb(9, 10, 11); color: rgb(9, 10, 11);
color: rgb(var(--dt-row-selected-link)); color: rgb(var(--dt-row-selected-link));
} }
table.dataTable.table-striped > tbody > tr.odd > * { table.table.dataTable.table-striped > tbody > tr:nth-of-type(2n+1) > * {
box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-stripe), 0.05); box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-stripe), 0.05);
} }
table.dataTable.table-striped > tbody > tr.odd.selected > * { table.table.dataTable.table-striped > tbody > tr:nth-of-type(2n+1).selected > * {
box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.95); box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.95);
box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.95); box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.95);
} }
table.dataTable.table-hover > tbody > tr:hover > * { table.table.dataTable.table-hover > tbody > tr:hover > * {
box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-hover), 0.075); box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-hover), 0.075);
} }
table.dataTable.table-hover > tbody > tr.selected:hover > * { table.table.dataTable.table-hover > tbody > tr.selected:hover > * {
box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.975); box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.975);
box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.975); box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.975);
} }
div.dataTables_wrapper div.dataTables_length label { div.dt-container div.dt-length label {
font-weight: normal; font-weight: normal;
text-align: left; text-align: left;
white-space: nowrap; white-space: nowrap;
} }
div.dataTables_wrapper div.dataTables_length select { div.dt-container div.dt-length select {
width: auto; width: auto;
display: inline-block; display: inline-block;
margin-right: 0.5em;
} }
div.dataTables_wrapper div.dataTables_filter { div.dt-container div.dt-search {
text-align: right; text-align: right;
} }
div.dataTables_wrapper div.dataTables_filter label { div.dt-container div.dt-search label {
font-weight: normal; font-weight: normal;
white-space: nowrap; white-space: nowrap;
text-align: left; text-align: left;
} }
div.dataTables_wrapper div.dataTables_filter input { div.dt-container div.dt-search input {
margin-left: 0.5em; margin-left: 0.5em;
display: inline-block; display: inline-block;
width: auto; width: auto;
} }
div.dataTables_wrapper div.dataTables_info { div.dt-container div.dt-info {
padding-top: 0.85em; padding-top: 0.85em;
} }
div.dataTables_wrapper div.dataTables_paginate { div.dt-container div.dt-paging {
margin: 0; margin: 0;
white-space: nowrap;
text-align: right;
} }
div.dataTables_wrapper div.dataTables_paginate ul.pagination { div.dt-container div.dt-paging ul.pagination {
margin: 2px 0; margin: 2px 0;
white-space: nowrap; flex-wrap: wrap;
justify-content: flex-end;
} }
div.dataTables_wrapper div.dt-row { div.dt-container div.dt-row {
position: relative; position: relative;
} }
div.dataTables_scrollHead table.dataTable { div.dt-scroll-head table.dataTable {
margin-bottom: 0 !important; margin-bottom: 0 !important;
} }
div.dataTables_scrollBody > table { div.dt-scroll-body {
border-bottom-color: var(--bs-border-color);
border-bottom-width: var(--bs-border-width);
border-bottom-style: solid;
}
div.dt-scroll-body > table {
border-top: none; border-top: none;
margin-top: 0 !important; margin-top: 0 !important;
margin-bottom: 0 !important; margin-bottom: 0 !important;
} }
div.dataTables_scrollBody > table > thead .sorting:before, div.dt-scroll-body > table > tbody > tr:first-child {
div.dataTables_scrollBody > table > thead .sorting_asc:before, border-top-width: 0;
div.dataTables_scrollBody > table > thead .sorting_desc:before,
div.dataTables_scrollBody > table > thead .sorting:after,
div.dataTables_scrollBody > table > thead .sorting_asc:after,
div.dataTables_scrollBody > table > thead .sorting_desc:after {
display: none;
} }
div.dataTables_scrollBody > table > tbody tr:first-child th, div.dt-scroll-body > table > thead > tr {
div.dataTables_scrollBody > table > tbody tr:first-child td { border-width: 0 !important;
border-top: none; }
div.dt-scroll-body > table > tbody > tr:last-child > * {
border-bottom: none;
} }
div.dataTables_scrollFoot > .dataTables_scrollFootInner { div.dt-scroll-foot > .dt-scroll-footInner {
box-sizing: content-box; box-sizing: content-box;
} }
div.dataTables_scrollFoot > .dataTables_scrollFootInner > table { div.dt-scroll-foot > .dt-scroll-footInner > table {
margin-top: 0 !important; margin-top: 0 !important;
border-top: none; border-top: none;
} }
div.dt-scroll-foot > .dt-scroll-footInner > table > tfoot > tr:first-child {
border-top-width: 0 !important;
}
@media screen and (max-width: 767px) { @media screen and (max-width: 767px) {
div.dataTables_wrapper div.dataTables_length, div.dt-container div.dt-length,
div.dataTables_wrapper div.dataTables_filter, div.dt-container div.dt-search,
div.dataTables_wrapper div.dataTables_info, div.dt-container div.dt-info,
div.dataTables_wrapper div.dataTables_paginate { div.dt-container div.dt-paging {
text-align: center; text-align: center;
} }
div.dataTables_wrapper div.dataTables_paginate ul.pagination { div.dt-container .row {
--bs-gutter-y: 0.5rem;
}
div.dt-container div.dt-paging ul.pagination {
justify-content: center !important; justify-content: center !important;
} }
} }
table.dataTable.table-sm > thead > tr > th:not(.sorting_disabled) { table.dataTable.table-sm > thead > tr > th:not(.sorting_disabled) {
padding-right: 20px; padding-right: 20px;
} }
table.dataTable.table-sm > thead > tr > th:not(.sorting_disabled):before, table.dataTable.table-sm > thead > tr > th:not(.sorting_disabled):after {
table.table-bordered.dataTable { right: 5px;
border-right-width: 0;
}
table.table-bordered.dataTable thead tr:first-child th,
table.table-bordered.dataTable thead tr:first-child td {
border-top-width: 1px;
}
table.table-bordered.dataTable th,
table.table-bordered.dataTable td {
border-left-width: 0;
}
table.table-bordered.dataTable th:first-child, table.table-bordered.dataTable th:first-child,
table.table-bordered.dataTable td:first-child,
table.table-bordered.dataTable td:first-child {
border-left-width: 1px;
}
table.table-bordered.dataTable th:last-child, table.table-bordered.dataTable th:last-child,
table.table-bordered.dataTable td:last-child,
table.table-bordered.dataTable td:last-child {
border-right-width: 1px;
}
table.table-bordered.dataTable th,
table.table-bordered.dataTable td {
border-bottom-width: 1px;
} }
div.dataTables_scrollHead table.table-bordered { div.dt-scroll-head table.table-bordered {
border-bottom-width: 0; border-bottom-width: 0;
} }
div.table-responsive > div.dataTables_wrapper > div.row { div.table-responsive > div.dt-container > div.row {
margin: 0; margin: 0;
} }
div.table-responsive > div.dataTables_wrapper > div.row > div[class^=col-]:first-child { div.table-responsive > div.dt-container > div.row > div[class^=col-]:first-child {
padding-left: 0; padding-left: 0;
} }
div.table-responsive > div.dataTables_wrapper > div.row > div[class^=col-]:last-child { div.table-responsive > div.dt-container > div.row > div[class^=col-]:last-child {
padding-right: 0; padding-right: 0;
} }

12153
src/static/scripts/datatables.js

File diff suppressed because it is too large

48
src/static/scripts/jquery-3.7.0.slim.js → src/static/scripts/jquery-3.7.1.slim.js

@ -1,12 +1,12 @@
/*! /*!
* jQuery JavaScript Library v3.7.0 -ajax,-ajax/jsonp,-ajax/load,-ajax/script,-ajax/var/location,-ajax/var/nonce,-ajax/var/rquery,-ajax/xhr,-manipulation/_evalUrl,-deprecated/ajax-event-alias,-effects,-effects/animatedSelector,-effects/Tween * jQuery JavaScript Library v3.7.1 -ajax,-ajax/jsonp,-ajax/load,-ajax/script,-ajax/var/location,-ajax/var/nonce,-ajax/var/rquery,-ajax/xhr,-manipulation/_evalUrl,-deprecated/ajax-event-alias,-effects,-effects/animatedSelector,-effects/Tween
* https://jquery.com/ * https://jquery.com/
* *
* Copyright OpenJS Foundation and other contributors * Copyright OpenJS Foundation and other contributors
* Released under the MIT license * Released under the MIT license
* https://jquery.org/license * https://jquery.org/license
* *
* Date: 2023-05-11T18:29Z * Date: 2023-08-28T13:37Z
*/ */
( function( global, factory ) { ( function( global, factory ) {
@ -147,7 +147,7 @@ function toType( obj ) {
var version = "3.7.0 -ajax,-ajax/jsonp,-ajax/load,-ajax/script,-ajax/var/location,-ajax/var/nonce,-ajax/var/rquery,-ajax/xhr,-manipulation/_evalUrl,-deprecated/ajax-event-alias,-effects,-effects/animatedSelector,-effects/Tween", var version = "3.7.1 -ajax,-ajax/jsonp,-ajax/load,-ajax/script,-ajax/var/location,-ajax/var/nonce,-ajax/var/rquery,-ajax/xhr,-manipulation/_evalUrl,-deprecated/ajax-event-alias,-effects,-effects/animatedSelector,-effects/Tween",
rhtmlSuffix = /HTML$/i, rhtmlSuffix = /HTML$/i,
@ -411,9 +411,14 @@ jQuery.extend( {
// Do not traverse comment nodes // Do not traverse comment nodes
ret += jQuery.text( node ); ret += jQuery.text( node );
} }
} else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { }
if ( nodeType === 1 || nodeType === 11 ) {
return elem.textContent; return elem.textContent;
} else if ( nodeType === 3 || nodeType === 4 ) { }
if ( nodeType === 9 ) {
return elem.documentElement.textContent;
}
if ( nodeType === 3 || nodeType === 4 ) {
return elem.nodeValue; return elem.nodeValue;
} }
@ -1126,12 +1131,17 @@ function setDocument( node ) {
documentElement.msMatchesSelector; documentElement.msMatchesSelector;
// Support: IE 9 - 11+, Edge 12 - 18+ // Support: IE 9 - 11+, Edge 12 - 18+
// Accessing iframe documents after unload throws "permission denied" errors (see trac-13936) // Accessing iframe documents after unload throws "permission denied" errors
// Support: IE 11+, Edge 17 - 18+ // (see trac-13936).
// IE/Edge sometimes throw a "Permission denied" error when strict-comparing // Limit the fix to IE & Edge Legacy; despite Edge 15+ implementing `matches`,
// two documents; shallow comparisons work. // all IE 9+ and Edge Legacy versions implement `msMatchesSelector` as well.
// eslint-disable-next-line eqeqeq if ( documentElement.msMatchesSelector &&
if ( preferredDoc != document &&
// Support: IE 11+, Edge 17 - 18+
// IE/Edge sometimes throw a "Permission denied" error when strict-comparing
// two documents; shallow comparisons work.
// eslint-disable-next-line eqeqeq
preferredDoc != document &&
( subWindow = document.defaultView ) && subWindow.top !== subWindow ) { ( subWindow = document.defaultView ) && subWindow.top !== subWindow ) {
// Support: IE 9 - 11+, Edge 12 - 18+ // Support: IE 9 - 11+, Edge 12 - 18+
@ -2694,12 +2704,12 @@ jQuery.find = find;
jQuery.expr[ ":" ] = jQuery.expr.pseudos; jQuery.expr[ ":" ] = jQuery.expr.pseudos;
jQuery.unique = jQuery.uniqueSort; jQuery.unique = jQuery.uniqueSort;
// These have always been private, but they used to be documented // These have always been private, but they used to be documented as part of
// as part of Sizzle so let's maintain them in the 3.x line // Sizzle so let's maintain them for now for backwards compatibility purposes.
// for backwards compatibility purposes.
find.compile = compile; find.compile = compile;
find.select = select; find.select = select;
find.setDocument = setDocument; find.setDocument = setDocument;
find.tokenize = tokenize;
find.escape = jQuery.escapeSelector; find.escape = jQuery.escapeSelector;
find.getText = jQuery.text; find.getText = jQuery.text;
@ -5913,7 +5923,7 @@ function domManip( collection, args, callback, ignored ) {
if ( hasScripts ) { if ( hasScripts ) {
doc = scripts[ scripts.length - 1 ].ownerDocument; doc = scripts[ scripts.length - 1 ].ownerDocument;
// Reenable scripts // Re-enable scripts
jQuery.map( scripts, restoreScript ); jQuery.map( scripts, restoreScript );
// Evaluate executable scripts on first document insertion // Evaluate executable scripts on first document insertion
@ -6370,7 +6380,7 @@ var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" );
trChild = document.createElement( "div" ); trChild = document.createElement( "div" );
table.style.cssText = "position:absolute;left:-11111px;border-collapse:separate"; table.style.cssText = "position:absolute;left:-11111px;border-collapse:separate";
tr.style.cssText = "border:1px solid"; tr.style.cssText = "box-sizing:content-box;border:1px solid";
// Support: Chrome 86+ // Support: Chrome 86+
// Height set through cssText does not get applied. // Height set through cssText does not get applied.
@ -6382,7 +6392,7 @@ var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" );
// In our bodyBackground.html iframe, // In our bodyBackground.html iframe,
// display for all div elements is set to "inline", // display for all div elements is set to "inline",
// which causes a problem only in Android 8 Chrome 86. // which causes a problem only in Android 8 Chrome 86.
// Ensuring the div is display: block // Ensuring the div is `display: block`
// gets around this issue. // gets around this issue.
trChild.style.display = "block"; trChild.style.display = "block";
@ -8451,7 +8461,9 @@ jQuery.fn.extend( {
}, },
hover: function( fnOver, fnOut ) { hover: function( fnOver, fnOut ) {
return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver ); return this
.on( "mouseenter", fnOver )
.on( "mouseleave", fnOut || fnOver );
} }
} ); } );

2
src/static/templates/admin/organizations.hbs

@ -59,7 +59,7 @@
</main> </main>
<link rel="stylesheet" href="{{urlpath}}/vw_static/datatables.css" /> <link rel="stylesheet" href="{{urlpath}}/vw_static/datatables.css" />
<script src="{{urlpath}}/vw_static/jquery-3.7.0.slim.js"></script> <script src="{{urlpath}}/vw_static/jquery-3.7.1.slim.js"></script>
<script src="{{urlpath}}/vw_static/datatables.js"></script> <script src="{{urlpath}}/vw_static/datatables.js"></script>
<script src="{{urlpath}}/vw_static/admin_organizations.js"></script> <script src="{{urlpath}}/vw_static/admin_organizations.js"></script>
<script src="{{urlpath}}/vw_static/jdenticon.js"></script> <script src="{{urlpath}}/vw_static/jdenticon.js"></script>

2
src/static/templates/admin/users.hbs

@ -140,7 +140,7 @@
</main> </main>
<link rel="stylesheet" href="{{urlpath}}/vw_static/datatables.css" /> <link rel="stylesheet" href="{{urlpath}}/vw_static/datatables.css" />
<script src="{{urlpath}}/vw_static/jquery-3.7.0.slim.js"></script> <script src="{{urlpath}}/vw_static/jquery-3.7.1.slim.js"></script>
<script src="{{urlpath}}/vw_static/datatables.js"></script> <script src="{{urlpath}}/vw_static/datatables.js"></script>
<script src="{{urlpath}}/vw_static/admin_users.js"></script> <script src="{{urlpath}}/vw_static/admin_users.js"></script>
<script src="{{urlpath}}/vw_static/jdenticon.js"></script> <script src="{{urlpath}}/vw_static/jdenticon.js"></script>

2
src/static/templates/email/change_email.hbs

@ -2,5 +2,5 @@ Your Email Change
<!----------------> <!---------------->
To finalize changing your email address enter the following code in web vault: {{token}} To finalize changing your email address enter the following code in web vault: {{token}}
If you did not try to change an email address, you can safely ignore this email. If you did not try to change your email address, contact your administrator.
{{> email/email_footer_text }} {{> email/email_footer_text }}

2
src/static/templates/email/change_email.html.hbs

@ -9,7 +9,7 @@ Your Email Change
</tr> </tr>
<tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;"> <tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
<td class="content-block last" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; margin: 0; -webkit-font-smoothing: antialiased; padding: 0; -webkit-text-size-adjust: none; text-align: center;" valign="top" align="center"> <td class="content-block last" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; margin: 0; -webkit-font-smoothing: antialiased; padding: 0; -webkit-text-size-adjust: none; text-align: center;" valign="top" align="center">
If you did not try to change an email address, you can safely ignore this email. If you did not try to change your email address, contact your administrator.
</td> </td>
</tr> </tr>
</table> </table>

42
src/util.rs

@ -1,11 +1,7 @@
// //
// Web Headers and caching // Web Headers and caching
// //
use std::{ use std::{collections::HashMap, io::Cursor, ops::Deref, path::Path};
collections::HashMap,
io::{Cursor, ErrorKind},
ops::Deref,
};
use num_traits::ToPrimitive; use num_traits::ToPrimitive;
use rocket::{ use rocket::{
@ -334,40 +330,6 @@ impl Fairing for BetterLogging {
} }
} }
//
// File handling
//
use std::{
fs::{self, File},
io::Result as IOResult,
path::Path,
};
pub fn file_exists(path: &str) -> bool {
Path::new(path).exists()
}
pub fn write_file(path: &str, content: &[u8]) -> Result<(), crate::error::Error> {
use std::io::Write;
let mut f = match File::create(path) {
Ok(file) => file,
Err(e) => {
if e.kind() == ErrorKind::PermissionDenied {
error!("Can't create '{}': Permission denied", path);
}
return Err(From::from(e));
}
};
f.write_all(content)?;
f.flush()?;
Ok(())
}
pub fn delete_file(path: &str) -> IOResult<()> {
fs::remove_file(path)
}
pub fn get_display_size(size: i64) -> String { pub fn get_display_size(size: i64) -> String {
const UNITS: [&str; 6] = ["bytes", "KB", "MB", "GB", "TB", "PB"]; const UNITS: [&str; 6] = ["bytes", "KB", "MB", "GB", "TB", "PB"];
@ -444,7 +406,7 @@ pub fn get_env_str_value(key: &str) -> Option<String> {
match (value_from_env, value_file) { match (value_from_env, value_file) {
(Ok(_), Ok(_)) => panic!("You should not define both {key} and {key_file}!"), (Ok(_), Ok(_)) => panic!("You should not define both {key} and {key_file}!"),
(Ok(v_env), Err(_)) => Some(v_env), (Ok(v_env), Err(_)) => Some(v_env),
(Err(_), Ok(v_file)) => match fs::read_to_string(v_file) { (Err(_), Ok(v_file)) => match std::fs::read_to_string(v_file) {
Ok(content) => Some(content.trim().to_string()), Ok(content) => Some(content.trim().to_string()),
Err(e) => panic!("Failed to load {key}: {e:?}"), Err(e) => panic!("Failed to load {key}: {e:?}"),
}, },

14
tools/global_domains.py

@ -10,19 +10,19 @@ import urllib.request
from collections import OrderedDict from collections import OrderedDict
if not (2 <= len(sys.argv) <= 3): if not 2 <= len(sys.argv) <= 3:
print("usage: %s <OUTPUT-FILE> [GIT-REF]" % sys.argv[0]) print(f"usage: {sys.argv[0]} <OUTPUT-FILE> [GIT-REF]")
print() print()
print("This script generates a global equivalent domains JSON file from") print("This script generates a global equivalent domains JSON file from")
print("the upstream Bitwarden source repo.") print("the upstream Bitwarden source repo.")
sys.exit(1) sys.exit(1)
OUTPUT_FILE = sys.argv[1] OUTPUT_FILE = sys.argv[1]
GIT_REF = 'master' if len(sys.argv) == 2 else sys.argv[2] GIT_REF = 'main' if len(sys.argv) == 2 else sys.argv[2]
BASE_URL = 'https://github.com/bitwarden/server/raw/%s' % GIT_REF BASE_URL = f'https://github.com/bitwarden/server/raw/{GIT_REF}'
ENUMS_URL = '%s/src/Core/Enums/GlobalEquivalentDomainsType.cs' % BASE_URL ENUMS_URL = f'{BASE_URL}/src/Core/Enums/GlobalEquivalentDomainsType.cs'
DOMAIN_LISTS_URL = '%s/src/Core/Utilities/StaticStore.cs' % BASE_URL DOMAIN_LISTS_URL = f'{BASE_URL}/src/Core/Utilities/StaticStore.cs'
# Enum lines look like: # Enum lines look like:
# #
@ -77,5 +77,5 @@ for name, domain_list in domain_lists.items():
global_domains.append(entry) global_domains.append(entry)
# Write out the global domains JSON file. # Write out the global domains JSON file.
with open(OUTPUT_FILE, 'w') as f: with open(file=OUTPUT_FILE, mode='w', encoding='utf-8') as f:
json.dump(global_domains, f, indent=2) json.dump(global_domains, f, indent=2)

Loading…
Cancel
Save