mirror of
https://github.com/dani-garcia/vaultwarden.git
synced 2025-09-10 18:55:57 +03:00
Compare commits
101 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
638766b346 | ||
|
d1ff136552 | ||
|
46ec11de12 | ||
|
4283a49e0b | ||
|
1e32db8c41 | ||
|
0f944ec7e2 | ||
|
736dbc9553 | ||
|
b4a38f1f63 | ||
|
646186fe38 | ||
|
c2725916f4 | ||
|
fd334e2b7d | ||
|
f9feca1ce4 | ||
|
677fd2ff32 | ||
|
f49eb8eb4d | ||
|
b0e0d68632 | ||
|
f3c8c16d79 | ||
|
2dd5086916 | ||
|
7532072d50 | ||
|
382e6107fe | ||
|
e6c6609e19 | ||
|
4cb5918950 | ||
|
55030f3687 | ||
|
ef4072e4ff | ||
|
c78d383ed1 | ||
|
5b96270874 | ||
|
2c0742387b | ||
|
1704d14f29 | ||
|
2d7ffbf378 | ||
|
dfd63f85c0 | ||
|
cd0c49eaf6 | ||
|
080e38d227 | ||
|
1a664fba6a | ||
|
c915ef815d | ||
|
adea4ec54d | ||
|
387b5eb2dd | ||
|
6337af59ed | ||
|
475c7b8f16 | ||
|
ac120be1c6 | ||
|
b70316e6d3 | ||
|
0a0f620d0b | ||
|
9132cc4a30 | ||
|
e50edcadfb | ||
|
2685099720 | ||
|
6fa6eb18e8 | ||
|
bb79396f0e | ||
|
da9fd6b7d0 | ||
|
5b8067ef77 | ||
|
9eabcd5cae | ||
|
d6e0d4cbbd | ||
|
e5e6db2688 | ||
|
186fe24484 | ||
|
5da96d36e6 | ||
|
f4b1071e23 | ||
|
18291b6533 | ||
|
8095cb68bb | ||
|
04cd751556 | ||
|
7ce2372f51 | ||
|
aebda93afe | ||
|
2b7b1141eb | ||
|
1ff4ff72bf | ||
|
d27e91a9b0 | ||
|
7cf063b196 | ||
|
642f04d493 | ||
|
fc6e65e4b0 | ||
|
db5c98ec3b | ||
|
73c64af27e | ||
|
b3f7db813f | ||
|
59660ff087 | ||
|
69a69e8e04 | ||
|
1094f359c3 | ||
|
102ee3f871 | ||
|
acb5ab08a8 | ||
|
ae59472d9a | ||
|
5a07b193dc | ||
|
fd2edb9adc | ||
|
1d074f7b3f | ||
|
81984c4bce | ||
|
9c891baad1 | ||
|
b050c60807 | ||
|
e47a2fd0f3 | ||
|
42b9cc73ac | ||
|
edca4248aa | ||
|
b1b6bc9be0 | ||
|
818b254cef | ||
|
ddfac5e34b | ||
|
8b5c945bad | ||
|
50c5eb9c50 | ||
|
94be67eac1 | ||
|
5a05139efe | ||
|
a62dc102fb | ||
|
518d74ce21 | ||
|
7598997deb | ||
|
3c876dc202 | ||
|
1722742ab3 | ||
|
d9c0eb3cfc | ||
|
0d990e1dc0 | ||
|
60ed5ff99d | ||
|
5b98bd66ee | ||
|
abd20777fe | ||
|
7f0d0cf8a4 | ||
|
6e23a573fb |
@@ -245,6 +245,10 @@
|
|||||||
## Name shown in the invitation emails that don't come from a specific organization
|
## Name shown in the invitation emails that don't come from a specific organization
|
||||||
# INVITATION_ORG_NAME=Vaultwarden
|
# INVITATION_ORG_NAME=Vaultwarden
|
||||||
|
|
||||||
|
## The number of hours after which an organization invite token, emergency access invite token,
|
||||||
|
## email verification token and deletion request token will expire (must be at least 1)
|
||||||
|
# INVITATION_EXPIRATION_HOURS=120
|
||||||
|
|
||||||
## Per-organization attachment storage limit (KB)
|
## Per-organization attachment storage limit (KB)
|
||||||
## Max kilobytes of attachment storage allowed per organization.
|
## Max kilobytes of attachment storage allowed per organization.
|
||||||
## When this limit is reached, organization members will not be allowed to upload further attachments for ciphers owned by that organization.
|
## When this limit is reached, organization members will not be allowed to upload further attachments for ciphers owned by that organization.
|
||||||
|
9
.github/workflows/build.yml
vendored
9
.github/workflows/build.yml
vendored
@@ -30,7 +30,10 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
channel:
|
channel:
|
||||||
- "rust-toolchain" # The version defined in rust-toolchain
|
- "rust-toolchain" # The version defined in rust-toolchain
|
||||||
- "1.59.0" # The supported MSRV
|
- "msrv" # The supported MSRV
|
||||||
|
include:
|
||||||
|
- channel: "msrv"
|
||||||
|
version: "1.60.0"
|
||||||
|
|
||||||
name: Build and Test ${{ matrix.channel }}
|
name: Build and Test ${{ matrix.channel }}
|
||||||
|
|
||||||
@@ -63,7 +66,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
profile: minimal
|
profile: minimal
|
||||||
override: true
|
override: true
|
||||||
toolchain: ${{ matrix.channel }}
|
toolchain: ${{ matrix.version }}
|
||||||
# End Install the MSRV channel to be used
|
# End Install the MSRV channel to be used
|
||||||
|
|
||||||
|
|
||||||
@@ -193,5 +196,5 @@ jobs:
|
|||||||
if: ${{ matrix.channel == 'rust-toolchain' }}
|
if: ${{ matrix.channel == 'rust-toolchain' }}
|
||||||
with:
|
with:
|
||||||
name: vaultwarden
|
name: vaultwarden
|
||||||
path: target/${{ matrix.target-triple }}/release/vaultwarden
|
path: target/release/vaultwarden
|
||||||
# End Upload artifact to Github Actions
|
# End Upload artifact to Github Actions
|
||||||
|
12
.github/workflows/hadolint.yml
vendored
12
.github/workflows/hadolint.yml
vendored
@@ -1,13 +1,9 @@
|
|||||||
name: Hadolint
|
name: Hadolint
|
||||||
|
|
||||||
on:
|
on: [
|
||||||
push:
|
push,
|
||||||
paths:
|
pull_request
|
||||||
- "docker/**"
|
]
|
||||||
|
|
||||||
pull_request:
|
|
||||||
paths:
|
|
||||||
- "docker/**"
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
hadolint:
|
hadolint:
|
||||||
|
924
Cargo.lock
generated
924
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
64
Cargo.toml
64
Cargo.toml
@@ -3,7 +3,7 @@ name = "vaultwarden"
|
|||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
authors = ["Daniel García <dani-garcia@users.noreply.github.com>"]
|
authors = ["Daniel García <dani-garcia@users.noreply.github.com>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
rust-version = "1.59"
|
rust-version = "1.60.0"
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
|
|
||||||
repository = "https://github.com/dani-garcia/vaultwarden"
|
repository = "https://github.com/dani-garcia/vaultwarden"
|
||||||
@@ -37,15 +37,15 @@ syslog = "6.0.1" # Needs to be v4 until fern is updated
|
|||||||
# Logging
|
# Logging
|
||||||
log = "0.4.17"
|
log = "0.4.17"
|
||||||
fern = { version = "0.6.1", features = ["syslog-6"] }
|
fern = { version = "0.6.1", features = ["syslog-6"] }
|
||||||
tracing = { version = "0.1.35", features = ["log"] } # Needed to have lettre and webauthn-rs trace logging to work
|
tracing = { version = "0.1.37", features = ["log"] } # Needed to have lettre and webauthn-rs trace logging to work
|
||||||
|
|
||||||
backtrace = "0.3.66" # Logging panics to logfile instead stderr only
|
backtrace = "0.3.66" # Logging panics to logfile instead stderr only
|
||||||
|
|
||||||
# A `dotenv` implementation for Rust
|
# A `dotenv` implementation for Rust
|
||||||
dotenvy = { version = "0.15.1", default-features = false }
|
dotenvy = { version = "0.15.5", default-features = false }
|
||||||
|
|
||||||
# Lazy initialization
|
# Lazy initialization
|
||||||
once_cell = "1.13.0"
|
once_cell = "1.15.0"
|
||||||
|
|
||||||
# Numerical libraries
|
# Numerical libraries
|
||||||
num-traits = "0.2.15"
|
num-traits = "0.2.15"
|
||||||
@@ -57,15 +57,15 @@ rocket = { version = "0.5.0-rc.2", features = ["tls", "json"], default-features
|
|||||||
# WebSockets libraries
|
# WebSockets libraries
|
||||||
tokio-tungstenite = "0.17.2"
|
tokio-tungstenite = "0.17.2"
|
||||||
rmpv = "1.0.0" # MessagePack library
|
rmpv = "1.0.0" # MessagePack library
|
||||||
dashmap = "5.3.4" # Concurrent hashmap implementation
|
dashmap = "5.4.0"
|
||||||
|
|
||||||
# Async futures
|
# Async futures
|
||||||
futures = "0.3.21"
|
futures = "0.3.24"
|
||||||
tokio = { version = "1.20.0", features = ["rt-multi-thread", "fs", "io-util", "parking_lot", "time"] }
|
tokio = { version = "1.21.2", features = ["rt-multi-thread", "fs", "io-util", "parking_lot", "time"] }
|
||||||
|
|
||||||
# A generic serialization/deserialization framework
|
# A generic serialization/deserialization framework
|
||||||
serde = { version = "1.0.139", features = ["derive"] }
|
serde = { version = "1.0.145", features = ["derive"] }
|
||||||
serde_json = "1.0.82"
|
serde_json = "1.0.86"
|
||||||
|
|
||||||
# A safe, extensible ORM and Query builder
|
# A safe, extensible ORM and Query builder
|
||||||
diesel = { version = "1.4.8", features = ["chrono", "r2d2"] }
|
diesel = { version = "1.4.8", features = ["chrono", "r2d2"] }
|
||||||
@@ -79,15 +79,15 @@ rand = { version = "0.8.5", features = ["small_rng"] }
|
|||||||
ring = "0.16.20"
|
ring = "0.16.20"
|
||||||
|
|
||||||
# UUID generation
|
# UUID generation
|
||||||
uuid = { version = "1.1.2", features = ["v4"] }
|
uuid = { version = "1.2.1", features = ["v4"] }
|
||||||
|
|
||||||
# Date and time libraries
|
# Date and time libraries
|
||||||
chrono = { version = "0.4.19", features = ["clock", "serde"], default-features = false }
|
chrono = { version = "0.4.22", features = ["clock", "serde"], default-features = false }
|
||||||
chrono-tz = "0.6.1"
|
chrono-tz = "0.6.3"
|
||||||
time = "0.3.11"
|
time = "0.3.15"
|
||||||
|
|
||||||
# Job scheduler
|
# Job scheduler
|
||||||
job_scheduler_ng = "2.0.1"
|
job_scheduler_ng = "2.0.2"
|
||||||
|
|
||||||
# Data encoding library Hex/Base32/Base64
|
# Data encoding library Hex/Base32/Base64
|
||||||
data-encoding = "2.3.2"
|
data-encoding = "2.3.2"
|
||||||
@@ -105,45 +105,51 @@ yubico = { version = "0.11.0", features = ["online-tokio"], default-features = f
|
|||||||
webauthn-rs = "0.3.2"
|
webauthn-rs = "0.3.2"
|
||||||
|
|
||||||
# Handling of URL's for WebAuthn
|
# Handling of URL's for WebAuthn
|
||||||
url = "2.2.2"
|
url = "2.3.1"
|
||||||
|
|
||||||
# Email librariese-Base, Update crates and small change.
|
# Email librariese-Base, Update crates and small change.
|
||||||
lettre = { version = "0.10.0", features = ["smtp-transport", "builder", "serde", "tokio1-native-tls", "hostname", "tracing", "tokio1"], default-features = false }
|
lettre = { version = "0.10.1", features = ["smtp-transport", "builder", "serde", "tokio1-native-tls", "hostname", "tracing", "tokio1"], default-features = false }
|
||||||
percent-encoding = "2.1.0" # URL encoding library used for URL's in the emails
|
percent-encoding = "2.2.0" # URL encoding library used for URL's in the emails
|
||||||
|
|
||||||
# Template library
|
# Template library
|
||||||
handlebars = { version = "4.3.2", features = ["dir_source"] }
|
handlebars = { version = "4.3.5", features = ["dir_source"] }
|
||||||
|
|
||||||
# HTTP client
|
# HTTP client
|
||||||
reqwest = { version = "0.11.11", features = ["stream", "json", "gzip", "brotli", "socks", "cookies", "trust-dns"] }
|
reqwest = { version = "0.11.12", features = ["stream", "json", "gzip", "brotli", "socks", "cookies", "trust-dns"] }
|
||||||
|
|
||||||
# For favicon extraction from main website
|
# For favicon extraction from main website
|
||||||
html5gum = "0.5.2"
|
html5gum = "0.5.2"
|
||||||
regex = { version = "1.6.0", features = ["std", "perf", "unicode-perl"], default-features = false }
|
regex = { version = "1.6.0", features = ["std", "perf", "unicode-perl"], default-features = false }
|
||||||
data-url = "0.1.1"
|
data-url = "0.2.0"
|
||||||
bytes = "1.1.0"
|
bytes = "1.2.1"
|
||||||
cached = "0.36.0"
|
cached = "0.39.0"
|
||||||
|
|
||||||
# Used for custom short lived cookie jar during favicon extraction
|
# Used for custom short lived cookie jar during favicon extraction
|
||||||
cookie = "0.16.0"
|
cookie = "0.16.1"
|
||||||
cookie_store = "0.16.1"
|
cookie_store = "0.17.0"
|
||||||
|
|
||||||
# Used by U2F, JWT and Postgres
|
# Used by U2F, JWT and Postgres
|
||||||
openssl = "0.10.41"
|
openssl = "0.10.42"
|
||||||
|
|
||||||
# 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.7"
|
paste = "1.0.9"
|
||||||
governor = "0.4.2"
|
governor = "0.5.0"
|
||||||
|
|
||||||
# Capture CTRL+C
|
# Capture CTRL+C
|
||||||
ctrlc = { version = "3.2.2", features = ["termination"] }
|
ctrlc = { version = "3.2.3", features = ["termination"] }
|
||||||
|
|
||||||
# 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
|
||||||
mimalloc = { version = "0.1.29", features = ["secure"], default-features = false, optional = true }
|
mimalloc = { version = "0.1.30", features = ["secure"], default-features = false, optional = true }
|
||||||
|
|
||||||
|
[patch.crates-io]
|
||||||
|
# Using a patched version of multer-rs (Used by Rocket) to fix attachment/send file uploads
|
||||||
|
# Issue: https://github.com/dani-garcia/vaultwarden/issues/2644
|
||||||
|
# Patch: https://github.com/BlackDex/multer-rs/commit/477d16b7fa0f361b5c2a5ba18a5b28bec6d26a8a
|
||||||
|
multer = { git = "https://github.com/BlackDex/multer-rs", rev = "477d16b7fa0f361b5c2a5ba18a5b28bec6d26a8a" }
|
||||||
|
|
||||||
# Strip debuginfo from the release builds
|
# Strip debuginfo from the release builds
|
||||||
# Also enable thin LTO for some optimizations
|
# Also enable thin LTO for some optimizations
|
||||||
|
@@ -7,12 +7,12 @@
|
|||||||
[](https://hub.docker.com/r/vaultwarden/server)
|
[](https://hub.docker.com/r/vaultwarden/server)
|
||||||
[](https://deps.rs/repo/github/dani-garcia/vaultwarden)
|
[](https://deps.rs/repo/github/dani-garcia/vaultwarden)
|
||||||
[](https://github.com/dani-garcia/vaultwarden/releases/latest)
|
[](https://github.com/dani-garcia/vaultwarden/releases/latest)
|
||||||
[](https://github.com/dani-garcia/vaultwarden/blob/master/LICENSE.txt)
|
[](https://github.com/dani-garcia/vaultwarden/blob/main/LICENSE.txt)
|
||||||
[](https://matrix.to/#/#vaultwarden:matrix.org)
|
[](https://matrix.to/#/#vaultwarden:matrix.org)
|
||||||
|
|
||||||
Image is based on [Rust implementation of Bitwarden API](https://github.com/dani-garcia/vaultwarden).
|
Image is based on [Rust implementation of Bitwarden API](https://github.com/dani-garcia/vaultwarden).
|
||||||
|
|
||||||
**This project is not associated with the [Bitwarden](https://bitwarden.com/) project nor 8bit Solutions LLC.**
|
**This project is not associated with the [Bitwarden](https://bitwarden.com/) project nor Bitwarden, Inc.**
|
||||||
|
|
||||||
#### ⚠️**IMPORTANT**⚠️: When using this server, please report any bugs or suggestions to us directly (look at the bottom of this page for ways to get in touch), regardless of whatever clients you are using (mobile, desktop, browser...). DO NOT use the official support channels.
|
#### ⚠️**IMPORTANT**⚠️: When using this server, please report any bugs or suggestions to us directly (look at the bottom of this page for ways to get in touch), regardless of whatever clients you are using (mobile, desktop, browser...). DO NOT use the official support channels.
|
||||||
|
|
||||||
|
@@ -3,23 +3,23 @@
|
|||||||
# This file was generated using a Jinja2 template.
|
# This file was generated using a Jinja2 template.
|
||||||
# Please make your changes in `Dockerfile.j2` and then `make` the individual Dockerfiles.
|
# Please make your changes in `Dockerfile.j2` and then `make` the individual Dockerfiles.
|
||||||
|
|
||||||
{% set build_stage_base_image = "rust:1.61-bullseye" %}
|
{% set build_stage_base_image = "rust:1.64-bullseye" %}
|
||||||
{% if "alpine" in target_file %}
|
{% if "alpine" in target_file %}
|
||||||
{% if "amd64" in target_file %}
|
{% if "amd64" in target_file %}
|
||||||
{% set build_stage_base_image = "blackdex/rust-musl:x86_64-musl-stable-1.61.0" %}
|
{% set build_stage_base_image = "blackdex/rust-musl:x86_64-musl-stable-1.64.0" %}
|
||||||
{% set runtime_stage_base_image = "alpine:3.15" %}
|
{% set runtime_stage_base_image = "alpine:3.16" %}
|
||||||
{% set package_arch_target = "x86_64-unknown-linux-musl" %}
|
{% set package_arch_target = "x86_64-unknown-linux-musl" %}
|
||||||
{% elif "armv7" in target_file %}
|
{% elif "armv7" in target_file %}
|
||||||
{% set build_stage_base_image = "blackdex/rust-musl:armv7-musleabihf-stable-1.61.0" %}
|
{% set build_stage_base_image = "blackdex/rust-musl:armv7-musleabihf-stable-1.64.0" %}
|
||||||
{% set runtime_stage_base_image = "balenalib/armv7hf-alpine:3.15" %}
|
{% set runtime_stage_base_image = "balenalib/armv7hf-alpine:3.16" %}
|
||||||
{% set package_arch_target = "armv7-unknown-linux-musleabihf" %}
|
{% set package_arch_target = "armv7-unknown-linux-musleabihf" %}
|
||||||
{% elif "armv6" in target_file %}
|
{% elif "armv6" in target_file %}
|
||||||
{% set build_stage_base_image = "blackdex/rust-musl:arm-musleabi-stable-1.61.0" %}
|
{% set build_stage_base_image = "blackdex/rust-musl:arm-musleabi-stable-1.64.0" %}
|
||||||
{% set runtime_stage_base_image = "balenalib/rpi-alpine:3.15" %}
|
{% set runtime_stage_base_image = "balenalib/rpi-alpine:3.16" %}
|
||||||
{% set package_arch_target = "arm-unknown-linux-musleabi" %}
|
{% set package_arch_target = "arm-unknown-linux-musleabi" %}
|
||||||
{% elif "arm64" in target_file %}
|
{% elif "arm64" in target_file %}
|
||||||
{% set build_stage_base_image = "blackdex/rust-musl:aarch64-musl-stable-1.61.0" %}
|
{% set build_stage_base_image = "blackdex/rust-musl:aarch64-musl-stable-1.64.0" %}
|
||||||
{% set runtime_stage_base_image = "balenalib/aarch64-alpine:3.15" %}
|
{% set runtime_stage_base_image = "balenalib/aarch64-alpine:3.16" %}
|
||||||
{% set package_arch_target = "aarch64-unknown-linux-musl" %}
|
{% set package_arch_target = "aarch64-unknown-linux-musl" %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% elif "amd64" in target_file %}
|
{% elif "amd64" in target_file %}
|
||||||
@@ -59,8 +59,8 @@
|
|||||||
# https://docs.docker.com/develop/develop-images/multistage-build/
|
# https://docs.docker.com/develop/develop-images/multistage-build/
|
||||||
# https://whitfin.io/speeding-up-rust-docker-builds/
|
# https://whitfin.io/speeding-up-rust-docker-builds/
|
||||||
####################### VAULT BUILD IMAGE #######################
|
####################### VAULT BUILD IMAGE #######################
|
||||||
{% set vault_version = "v2022.6.2" %}
|
{% set vault_version = "v2022.10.0" %}
|
||||||
{% set vault_image_digest = "sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70" %}
|
{% set vault_image_digest = "sha256:8e8405d252bb6ecc7d59d90e9ba9dde09f35c1b6858371274c67c3e0a6f14a80" %}
|
||||||
# The web-vault digest specifies a particular web-vault build on Docker Hub.
|
# The web-vault digest specifies a particular web-vault build on Docker Hub.
|
||||||
# Using the digest instead of the tag name provides better security,
|
# Using the digest instead of the tag name provides better security,
|
||||||
# as the digest of an image is immutable, whereas a tag name can later
|
# as the digest of an image is immutable, whereas a tag name can later
|
||||||
@@ -206,7 +206,6 @@ RUN mkdir /data \
|
|||||||
openssl \
|
openssl \
|
||||||
tzdata \
|
tzdata \
|
||||||
curl \
|
curl \
|
||||||
dumb-init \
|
|
||||||
ca-certificates
|
ca-certificates
|
||||||
{% else %}
|
{% else %}
|
||||||
&& apt-get update && apt-get install -y \
|
&& apt-get update && apt-get install -y \
|
||||||
@@ -214,7 +213,6 @@ RUN mkdir /data \
|
|||||||
openssl \
|
openssl \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
curl \
|
curl \
|
||||||
dumb-init \
|
|
||||||
libmariadb-dev-compat \
|
libmariadb-dev-compat \
|
||||||
libpq5 \
|
libpq5 \
|
||||||
&& apt-get clean \
|
&& apt-get clean \
|
||||||
@@ -253,10 +251,4 @@ COPY docker/start.sh /start.sh
|
|||||||
|
|
||||||
HEALTHCHECK --interval=60s --timeout=10s CMD ["/healthcheck.sh"]
|
HEALTHCHECK --interval=60s --timeout=10s CMD ["/healthcheck.sh"]
|
||||||
|
|
||||||
# Configures the startup!
|
|
||||||
# We should be able to remove the dumb-init now with Rocket 0.5
|
|
||||||
# But the balenalib images have some issues with there entry.sh
|
|
||||||
# See: https://github.com/balena-io-library/base-images/issues/735
|
|
||||||
# Lets keep using dumb-init for now, since that is working fine.
|
|
||||||
ENTRYPOINT ["/usr/bin/dumb-init", "--"]
|
|
||||||
CMD ["/start.sh"]
|
CMD ["/start.sh"]
|
||||||
|
@@ -16,18 +16,18 @@
|
|||||||
# - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
|
# - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
|
||||||
# click the tag name to view the digest of the image it currently points to.
|
# click the tag name to view the digest of the image it currently points to.
|
||||||
# - From the command line:
|
# - From the command line:
|
||||||
# $ docker pull vaultwarden/web-vault:v2022.6.2
|
# $ docker pull vaultwarden/web-vault:v2022.10.0
|
||||||
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2022.6.2
|
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2022.10.0
|
||||||
# [vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70]
|
# [vaultwarden/web-vault@sha256:8e8405d252bb6ecc7d59d90e9ba9dde09f35c1b6858371274c67c3e0a6f14a80]
|
||||||
#
|
#
|
||||||
# - Conversely, to get the tag name from the digest:
|
# - Conversely, to get the tag name from the digest:
|
||||||
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70
|
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:8e8405d252bb6ecc7d59d90e9ba9dde09f35c1b6858371274c67c3e0a6f14a80
|
||||||
# [vaultwarden/web-vault:v2022.6.2]
|
# [vaultwarden/web-vault:v2022.10.0]
|
||||||
#
|
#
|
||||||
FROM vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70 as vault
|
FROM vaultwarden/web-vault@sha256:8e8405d252bb6ecc7d59d90e9ba9dde09f35c1b6858371274c67c3e0a6f14a80 as vault
|
||||||
|
|
||||||
########################## BUILD IMAGE ##########################
|
########################## BUILD IMAGE ##########################
|
||||||
FROM rust:1.61-bullseye as build
|
FROM rust:1.64-bullseye as build
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -101,7 +101,6 @@ RUN mkdir /data \
|
|||||||
openssl \
|
openssl \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
curl \
|
curl \
|
||||||
dumb-init \
|
|
||||||
libmariadb-dev-compat \
|
libmariadb-dev-compat \
|
||||||
libpq5 \
|
libpq5 \
|
||||||
&& apt-get clean \
|
&& apt-get clean \
|
||||||
@@ -123,10 +122,4 @@ COPY docker/start.sh /start.sh
|
|||||||
|
|
||||||
HEALTHCHECK --interval=60s --timeout=10s CMD ["/healthcheck.sh"]
|
HEALTHCHECK --interval=60s --timeout=10s CMD ["/healthcheck.sh"]
|
||||||
|
|
||||||
# Configures the startup!
|
|
||||||
# We should be able to remove the dumb-init now with Rocket 0.5
|
|
||||||
# But the balenalib images have some issues with there entry.sh
|
|
||||||
# See: https://github.com/balena-io-library/base-images/issues/735
|
|
||||||
# Lets keep using dumb-init for now, since that is working fine.
|
|
||||||
ENTRYPOINT ["/usr/bin/dumb-init", "--"]
|
|
||||||
CMD ["/start.sh"]
|
CMD ["/start.sh"]
|
||||||
|
@@ -16,18 +16,18 @@
|
|||||||
# - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
|
# - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
|
||||||
# click the tag name to view the digest of the image it currently points to.
|
# click the tag name to view the digest of the image it currently points to.
|
||||||
# - From the command line:
|
# - From the command line:
|
||||||
# $ docker pull vaultwarden/web-vault:v2022.6.2
|
# $ docker pull vaultwarden/web-vault:v2022.10.0
|
||||||
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2022.6.2
|
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2022.10.0
|
||||||
# [vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70]
|
# [vaultwarden/web-vault@sha256:8e8405d252bb6ecc7d59d90e9ba9dde09f35c1b6858371274c67c3e0a6f14a80]
|
||||||
#
|
#
|
||||||
# - Conversely, to get the tag name from the digest:
|
# - Conversely, to get the tag name from the digest:
|
||||||
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70
|
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:8e8405d252bb6ecc7d59d90e9ba9dde09f35c1b6858371274c67c3e0a6f14a80
|
||||||
# [vaultwarden/web-vault:v2022.6.2]
|
# [vaultwarden/web-vault:v2022.10.0]
|
||||||
#
|
#
|
||||||
FROM vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70 as vault
|
FROM vaultwarden/web-vault@sha256:8e8405d252bb6ecc7d59d90e9ba9dde09f35c1b6858371274c67c3e0a6f14a80 as vault
|
||||||
|
|
||||||
########################## BUILD IMAGE ##########################
|
########################## BUILD IMAGE ##########################
|
||||||
FROM blackdex/rust-musl:x86_64-musl-stable-1.61.0 as build
|
FROM blackdex/rust-musl:x86_64-musl-stable-1.64.0 as build
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -81,7 +81,7 @@ RUN cargo build --features ${DB} --release --target=x86_64-unknown-linux-musl
|
|||||||
######################## RUNTIME IMAGE ########################
|
######################## RUNTIME IMAGE ########################
|
||||||
# Create a new stage with a minimal image
|
# Create a new stage with a minimal image
|
||||||
# because we already have a binary built
|
# because we already have a binary built
|
||||||
FROM alpine:3.15
|
FROM alpine:3.16
|
||||||
|
|
||||||
ENV ROCKET_PROFILE="release" \
|
ENV ROCKET_PROFILE="release" \
|
||||||
ROCKET_ADDRESS=0.0.0.0 \
|
ROCKET_ADDRESS=0.0.0.0 \
|
||||||
@@ -96,7 +96,6 @@ RUN mkdir /data \
|
|||||||
openssl \
|
openssl \
|
||||||
tzdata \
|
tzdata \
|
||||||
curl \
|
curl \
|
||||||
dumb-init \
|
|
||||||
ca-certificates
|
ca-certificates
|
||||||
|
|
||||||
|
|
||||||
@@ -115,10 +114,4 @@ COPY docker/start.sh /start.sh
|
|||||||
|
|
||||||
HEALTHCHECK --interval=60s --timeout=10s CMD ["/healthcheck.sh"]
|
HEALTHCHECK --interval=60s --timeout=10s CMD ["/healthcheck.sh"]
|
||||||
|
|
||||||
# Configures the startup!
|
|
||||||
# We should be able to remove the dumb-init now with Rocket 0.5
|
|
||||||
# But the balenalib images have some issues with there entry.sh
|
|
||||||
# See: https://github.com/balena-io-library/base-images/issues/735
|
|
||||||
# Lets keep using dumb-init for now, since that is working fine.
|
|
||||||
ENTRYPOINT ["/usr/bin/dumb-init", "--"]
|
|
||||||
CMD ["/start.sh"]
|
CMD ["/start.sh"]
|
||||||
|
@@ -16,18 +16,18 @@
|
|||||||
# - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
|
# - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
|
||||||
# click the tag name to view the digest of the image it currently points to.
|
# click the tag name to view the digest of the image it currently points to.
|
||||||
# - From the command line:
|
# - From the command line:
|
||||||
# $ docker pull vaultwarden/web-vault:v2022.6.2
|
# $ docker pull vaultwarden/web-vault:v2022.10.0
|
||||||
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2022.6.2
|
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2022.10.0
|
||||||
# [vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70]
|
# [vaultwarden/web-vault@sha256:8e8405d252bb6ecc7d59d90e9ba9dde09f35c1b6858371274c67c3e0a6f14a80]
|
||||||
#
|
#
|
||||||
# - Conversely, to get the tag name from the digest:
|
# - Conversely, to get the tag name from the digest:
|
||||||
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70
|
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:8e8405d252bb6ecc7d59d90e9ba9dde09f35c1b6858371274c67c3e0a6f14a80
|
||||||
# [vaultwarden/web-vault:v2022.6.2]
|
# [vaultwarden/web-vault:v2022.10.0]
|
||||||
#
|
#
|
||||||
FROM vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70 as vault
|
FROM vaultwarden/web-vault@sha256:8e8405d252bb6ecc7d59d90e9ba9dde09f35c1b6858371274c67c3e0a6f14a80 as vault
|
||||||
|
|
||||||
########################## BUILD IMAGE ##########################
|
########################## BUILD IMAGE ##########################
|
||||||
FROM rust:1.61-bullseye as build
|
FROM rust:1.64-bullseye as build
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -101,7 +101,6 @@ RUN mkdir /data \
|
|||||||
openssl \
|
openssl \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
curl \
|
curl \
|
||||||
dumb-init \
|
|
||||||
libmariadb-dev-compat \
|
libmariadb-dev-compat \
|
||||||
libpq5 \
|
libpq5 \
|
||||||
&& apt-get clean \
|
&& apt-get clean \
|
||||||
@@ -123,10 +122,4 @@ COPY docker/start.sh /start.sh
|
|||||||
|
|
||||||
HEALTHCHECK --interval=60s --timeout=10s CMD ["/healthcheck.sh"]
|
HEALTHCHECK --interval=60s --timeout=10s CMD ["/healthcheck.sh"]
|
||||||
|
|
||||||
# Configures the startup!
|
|
||||||
# We should be able to remove the dumb-init now with Rocket 0.5
|
|
||||||
# But the balenalib images have some issues with there entry.sh
|
|
||||||
# See: https://github.com/balena-io-library/base-images/issues/735
|
|
||||||
# Lets keep using dumb-init for now, since that is working fine.
|
|
||||||
ENTRYPOINT ["/usr/bin/dumb-init", "--"]
|
|
||||||
CMD ["/start.sh"]
|
CMD ["/start.sh"]
|
||||||
|
@@ -16,18 +16,18 @@
|
|||||||
# - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
|
# - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
|
||||||
# click the tag name to view the digest of the image it currently points to.
|
# click the tag name to view the digest of the image it currently points to.
|
||||||
# - From the command line:
|
# - From the command line:
|
||||||
# $ docker pull vaultwarden/web-vault:v2022.6.2
|
# $ docker pull vaultwarden/web-vault:v2022.10.0
|
||||||
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2022.6.2
|
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2022.10.0
|
||||||
# [vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70]
|
# [vaultwarden/web-vault@sha256:8e8405d252bb6ecc7d59d90e9ba9dde09f35c1b6858371274c67c3e0a6f14a80]
|
||||||
#
|
#
|
||||||
# - Conversely, to get the tag name from the digest:
|
# - Conversely, to get the tag name from the digest:
|
||||||
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70
|
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:8e8405d252bb6ecc7d59d90e9ba9dde09f35c1b6858371274c67c3e0a6f14a80
|
||||||
# [vaultwarden/web-vault:v2022.6.2]
|
# [vaultwarden/web-vault:v2022.10.0]
|
||||||
#
|
#
|
||||||
FROM vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70 as vault
|
FROM vaultwarden/web-vault@sha256:8e8405d252bb6ecc7d59d90e9ba9dde09f35c1b6858371274c67c3e0a6f14a80 as vault
|
||||||
|
|
||||||
########################## BUILD IMAGE ##########################
|
########################## BUILD IMAGE ##########################
|
||||||
FROM blackdex/rust-musl:x86_64-musl-stable-1.61.0 as build
|
FROM blackdex/rust-musl:x86_64-musl-stable-1.64.0 as build
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -81,7 +81,7 @@ RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/.
|
|||||||
######################## RUNTIME IMAGE ########################
|
######################## RUNTIME IMAGE ########################
|
||||||
# Create a new stage with a minimal image
|
# Create a new stage with a minimal image
|
||||||
# because we already have a binary built
|
# because we already have a binary built
|
||||||
FROM alpine:3.15
|
FROM alpine:3.16
|
||||||
|
|
||||||
ENV ROCKET_PROFILE="release" \
|
ENV ROCKET_PROFILE="release" \
|
||||||
ROCKET_ADDRESS=0.0.0.0 \
|
ROCKET_ADDRESS=0.0.0.0 \
|
||||||
@@ -96,7 +96,6 @@ RUN mkdir /data \
|
|||||||
openssl \
|
openssl \
|
||||||
tzdata \
|
tzdata \
|
||||||
curl \
|
curl \
|
||||||
dumb-init \
|
|
||||||
ca-certificates
|
ca-certificates
|
||||||
|
|
||||||
|
|
||||||
@@ -115,10 +114,4 @@ COPY docker/start.sh /start.sh
|
|||||||
|
|
||||||
HEALTHCHECK --interval=60s --timeout=10s CMD ["/healthcheck.sh"]
|
HEALTHCHECK --interval=60s --timeout=10s CMD ["/healthcheck.sh"]
|
||||||
|
|
||||||
# Configures the startup!
|
|
||||||
# We should be able to remove the dumb-init now with Rocket 0.5
|
|
||||||
# But the balenalib images have some issues with there entry.sh
|
|
||||||
# See: https://github.com/balena-io-library/base-images/issues/735
|
|
||||||
# Lets keep using dumb-init for now, since that is working fine.
|
|
||||||
ENTRYPOINT ["/usr/bin/dumb-init", "--"]
|
|
||||||
CMD ["/start.sh"]
|
CMD ["/start.sh"]
|
||||||
|
@@ -16,18 +16,18 @@
|
|||||||
# - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
|
# - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
|
||||||
# click the tag name to view the digest of the image it currently points to.
|
# click the tag name to view the digest of the image it currently points to.
|
||||||
# - From the command line:
|
# - From the command line:
|
||||||
# $ docker pull vaultwarden/web-vault:v2022.6.2
|
# $ docker pull vaultwarden/web-vault:v2022.10.0
|
||||||
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2022.6.2
|
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2022.10.0
|
||||||
# [vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70]
|
# [vaultwarden/web-vault@sha256:8e8405d252bb6ecc7d59d90e9ba9dde09f35c1b6858371274c67c3e0a6f14a80]
|
||||||
#
|
#
|
||||||
# - Conversely, to get the tag name from the digest:
|
# - Conversely, to get the tag name from the digest:
|
||||||
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70
|
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:8e8405d252bb6ecc7d59d90e9ba9dde09f35c1b6858371274c67c3e0a6f14a80
|
||||||
# [vaultwarden/web-vault:v2022.6.2]
|
# [vaultwarden/web-vault:v2022.10.0]
|
||||||
#
|
#
|
||||||
FROM vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70 as vault
|
FROM vaultwarden/web-vault@sha256:8e8405d252bb6ecc7d59d90e9ba9dde09f35c1b6858371274c67c3e0a6f14a80 as vault
|
||||||
|
|
||||||
########################## BUILD IMAGE ##########################
|
########################## BUILD IMAGE ##########################
|
||||||
FROM rust:1.61-bullseye as build
|
FROM rust:1.64-bullseye as build
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -123,7 +123,6 @@ RUN mkdir /data \
|
|||||||
openssl \
|
openssl \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
curl \
|
curl \
|
||||||
dumb-init \
|
|
||||||
libmariadb-dev-compat \
|
libmariadb-dev-compat \
|
||||||
libpq5 \
|
libpq5 \
|
||||||
&& apt-get clean \
|
&& apt-get clean \
|
||||||
@@ -147,10 +146,4 @@ COPY docker/start.sh /start.sh
|
|||||||
|
|
||||||
HEALTHCHECK --interval=60s --timeout=10s CMD ["/healthcheck.sh"]
|
HEALTHCHECK --interval=60s --timeout=10s CMD ["/healthcheck.sh"]
|
||||||
|
|
||||||
# Configures the startup!
|
|
||||||
# We should be able to remove the dumb-init now with Rocket 0.5
|
|
||||||
# But the balenalib images have some issues with there entry.sh
|
|
||||||
# See: https://github.com/balena-io-library/base-images/issues/735
|
|
||||||
# Lets keep using dumb-init for now, since that is working fine.
|
|
||||||
ENTRYPOINT ["/usr/bin/dumb-init", "--"]
|
|
||||||
CMD ["/start.sh"]
|
CMD ["/start.sh"]
|
||||||
|
@@ -16,18 +16,18 @@
|
|||||||
# - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
|
# - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
|
||||||
# click the tag name to view the digest of the image it currently points to.
|
# click the tag name to view the digest of the image it currently points to.
|
||||||
# - From the command line:
|
# - From the command line:
|
||||||
# $ docker pull vaultwarden/web-vault:v2022.6.2
|
# $ docker pull vaultwarden/web-vault:v2022.10.0
|
||||||
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2022.6.2
|
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2022.10.0
|
||||||
# [vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70]
|
# [vaultwarden/web-vault@sha256:8e8405d252bb6ecc7d59d90e9ba9dde09f35c1b6858371274c67c3e0a6f14a80]
|
||||||
#
|
#
|
||||||
# - Conversely, to get the tag name from the digest:
|
# - Conversely, to get the tag name from the digest:
|
||||||
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70
|
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:8e8405d252bb6ecc7d59d90e9ba9dde09f35c1b6858371274c67c3e0a6f14a80
|
||||||
# [vaultwarden/web-vault:v2022.6.2]
|
# [vaultwarden/web-vault:v2022.10.0]
|
||||||
#
|
#
|
||||||
FROM vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70 as vault
|
FROM vaultwarden/web-vault@sha256:8e8405d252bb6ecc7d59d90e9ba9dde09f35c1b6858371274c67c3e0a6f14a80 as vault
|
||||||
|
|
||||||
########################## BUILD IMAGE ##########################
|
########################## BUILD IMAGE ##########################
|
||||||
FROM blackdex/rust-musl:aarch64-musl-stable-1.61.0 as build
|
FROM blackdex/rust-musl:aarch64-musl-stable-1.64.0 as build
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -81,7 +81,7 @@ RUN cargo build --features ${DB} --release --target=aarch64-unknown-linux-musl
|
|||||||
######################## RUNTIME IMAGE ########################
|
######################## RUNTIME IMAGE ########################
|
||||||
# Create a new stage with a minimal image
|
# Create a new stage with a minimal image
|
||||||
# because we already have a binary built
|
# because we already have a binary built
|
||||||
FROM balenalib/aarch64-alpine:3.15
|
FROM balenalib/aarch64-alpine:3.16
|
||||||
|
|
||||||
ENV ROCKET_PROFILE="release" \
|
ENV ROCKET_PROFILE="release" \
|
||||||
ROCKET_ADDRESS=0.0.0.0 \
|
ROCKET_ADDRESS=0.0.0.0 \
|
||||||
@@ -98,7 +98,6 @@ RUN mkdir /data \
|
|||||||
openssl \
|
openssl \
|
||||||
tzdata \
|
tzdata \
|
||||||
curl \
|
curl \
|
||||||
dumb-init \
|
|
||||||
ca-certificates
|
ca-certificates
|
||||||
|
|
||||||
# hadolint ignore=DL3059
|
# hadolint ignore=DL3059
|
||||||
@@ -119,10 +118,4 @@ COPY docker/start.sh /start.sh
|
|||||||
|
|
||||||
HEALTHCHECK --interval=60s --timeout=10s CMD ["/healthcheck.sh"]
|
HEALTHCHECK --interval=60s --timeout=10s CMD ["/healthcheck.sh"]
|
||||||
|
|
||||||
# Configures the startup!
|
|
||||||
# We should be able to remove the dumb-init now with Rocket 0.5
|
|
||||||
# But the balenalib images have some issues with there entry.sh
|
|
||||||
# See: https://github.com/balena-io-library/base-images/issues/735
|
|
||||||
# Lets keep using dumb-init for now, since that is working fine.
|
|
||||||
ENTRYPOINT ["/usr/bin/dumb-init", "--"]
|
|
||||||
CMD ["/start.sh"]
|
CMD ["/start.sh"]
|
||||||
|
@@ -16,18 +16,18 @@
|
|||||||
# - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
|
# - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
|
||||||
# click the tag name to view the digest of the image it currently points to.
|
# click the tag name to view the digest of the image it currently points to.
|
||||||
# - From the command line:
|
# - From the command line:
|
||||||
# $ docker pull vaultwarden/web-vault:v2022.6.2
|
# $ docker pull vaultwarden/web-vault:v2022.10.0
|
||||||
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2022.6.2
|
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2022.10.0
|
||||||
# [vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70]
|
# [vaultwarden/web-vault@sha256:8e8405d252bb6ecc7d59d90e9ba9dde09f35c1b6858371274c67c3e0a6f14a80]
|
||||||
#
|
#
|
||||||
# - Conversely, to get the tag name from the digest:
|
# - Conversely, to get the tag name from the digest:
|
||||||
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70
|
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:8e8405d252bb6ecc7d59d90e9ba9dde09f35c1b6858371274c67c3e0a6f14a80
|
||||||
# [vaultwarden/web-vault:v2022.6.2]
|
# [vaultwarden/web-vault:v2022.10.0]
|
||||||
#
|
#
|
||||||
FROM vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70 as vault
|
FROM vaultwarden/web-vault@sha256:8e8405d252bb6ecc7d59d90e9ba9dde09f35c1b6858371274c67c3e0a6f14a80 as vault
|
||||||
|
|
||||||
########################## BUILD IMAGE ##########################
|
########################## BUILD IMAGE ##########################
|
||||||
FROM rust:1.61-bullseye as build
|
FROM rust:1.64-bullseye as build
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -123,7 +123,6 @@ RUN mkdir /data \
|
|||||||
openssl \
|
openssl \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
curl \
|
curl \
|
||||||
dumb-init \
|
|
||||||
libmariadb-dev-compat \
|
libmariadb-dev-compat \
|
||||||
libpq5 \
|
libpq5 \
|
||||||
&& apt-get clean \
|
&& apt-get clean \
|
||||||
@@ -147,10 +146,4 @@ COPY docker/start.sh /start.sh
|
|||||||
|
|
||||||
HEALTHCHECK --interval=60s --timeout=10s CMD ["/healthcheck.sh"]
|
HEALTHCHECK --interval=60s --timeout=10s CMD ["/healthcheck.sh"]
|
||||||
|
|
||||||
# Configures the startup!
|
|
||||||
# We should be able to remove the dumb-init now with Rocket 0.5
|
|
||||||
# But the balenalib images have some issues with there entry.sh
|
|
||||||
# See: https://github.com/balena-io-library/base-images/issues/735
|
|
||||||
# Lets keep using dumb-init for now, since that is working fine.
|
|
||||||
ENTRYPOINT ["/usr/bin/dumb-init", "--"]
|
|
||||||
CMD ["/start.sh"]
|
CMD ["/start.sh"]
|
||||||
|
@@ -16,18 +16,18 @@
|
|||||||
# - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
|
# - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
|
||||||
# click the tag name to view the digest of the image it currently points to.
|
# click the tag name to view the digest of the image it currently points to.
|
||||||
# - From the command line:
|
# - From the command line:
|
||||||
# $ docker pull vaultwarden/web-vault:v2022.6.2
|
# $ docker pull vaultwarden/web-vault:v2022.10.0
|
||||||
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2022.6.2
|
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2022.10.0
|
||||||
# [vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70]
|
# [vaultwarden/web-vault@sha256:8e8405d252bb6ecc7d59d90e9ba9dde09f35c1b6858371274c67c3e0a6f14a80]
|
||||||
#
|
#
|
||||||
# - Conversely, to get the tag name from the digest:
|
# - Conversely, to get the tag name from the digest:
|
||||||
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70
|
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:8e8405d252bb6ecc7d59d90e9ba9dde09f35c1b6858371274c67c3e0a6f14a80
|
||||||
# [vaultwarden/web-vault:v2022.6.2]
|
# [vaultwarden/web-vault:v2022.10.0]
|
||||||
#
|
#
|
||||||
FROM vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70 as vault
|
FROM vaultwarden/web-vault@sha256:8e8405d252bb6ecc7d59d90e9ba9dde09f35c1b6858371274c67c3e0a6f14a80 as vault
|
||||||
|
|
||||||
########################## BUILD IMAGE ##########################
|
########################## BUILD IMAGE ##########################
|
||||||
FROM blackdex/rust-musl:aarch64-musl-stable-1.61.0 as build
|
FROM blackdex/rust-musl:aarch64-musl-stable-1.64.0 as build
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -81,7 +81,7 @@ RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/.
|
|||||||
######################## RUNTIME IMAGE ########################
|
######################## RUNTIME IMAGE ########################
|
||||||
# Create a new stage with a minimal image
|
# Create a new stage with a minimal image
|
||||||
# because we already have a binary built
|
# because we already have a binary built
|
||||||
FROM balenalib/aarch64-alpine:3.15
|
FROM balenalib/aarch64-alpine:3.16
|
||||||
|
|
||||||
ENV ROCKET_PROFILE="release" \
|
ENV ROCKET_PROFILE="release" \
|
||||||
ROCKET_ADDRESS=0.0.0.0 \
|
ROCKET_ADDRESS=0.0.0.0 \
|
||||||
@@ -98,7 +98,6 @@ RUN mkdir /data \
|
|||||||
openssl \
|
openssl \
|
||||||
tzdata \
|
tzdata \
|
||||||
curl \
|
curl \
|
||||||
dumb-init \
|
|
||||||
ca-certificates
|
ca-certificates
|
||||||
|
|
||||||
# hadolint ignore=DL3059
|
# hadolint ignore=DL3059
|
||||||
@@ -119,10 +118,4 @@ COPY docker/start.sh /start.sh
|
|||||||
|
|
||||||
HEALTHCHECK --interval=60s --timeout=10s CMD ["/healthcheck.sh"]
|
HEALTHCHECK --interval=60s --timeout=10s CMD ["/healthcheck.sh"]
|
||||||
|
|
||||||
# Configures the startup!
|
|
||||||
# We should be able to remove the dumb-init now with Rocket 0.5
|
|
||||||
# But the balenalib images have some issues with there entry.sh
|
|
||||||
# See: https://github.com/balena-io-library/base-images/issues/735
|
|
||||||
# Lets keep using dumb-init for now, since that is working fine.
|
|
||||||
ENTRYPOINT ["/usr/bin/dumb-init", "--"]
|
|
||||||
CMD ["/start.sh"]
|
CMD ["/start.sh"]
|
||||||
|
@@ -16,18 +16,18 @@
|
|||||||
# - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
|
# - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
|
||||||
# click the tag name to view the digest of the image it currently points to.
|
# click the tag name to view the digest of the image it currently points to.
|
||||||
# - From the command line:
|
# - From the command line:
|
||||||
# $ docker pull vaultwarden/web-vault:v2022.6.2
|
# $ docker pull vaultwarden/web-vault:v2022.10.0
|
||||||
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2022.6.2
|
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2022.10.0
|
||||||
# [vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70]
|
# [vaultwarden/web-vault@sha256:8e8405d252bb6ecc7d59d90e9ba9dde09f35c1b6858371274c67c3e0a6f14a80]
|
||||||
#
|
#
|
||||||
# - Conversely, to get the tag name from the digest:
|
# - Conversely, to get the tag name from the digest:
|
||||||
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70
|
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:8e8405d252bb6ecc7d59d90e9ba9dde09f35c1b6858371274c67c3e0a6f14a80
|
||||||
# [vaultwarden/web-vault:v2022.6.2]
|
# [vaultwarden/web-vault:v2022.10.0]
|
||||||
#
|
#
|
||||||
FROM vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70 as vault
|
FROM vaultwarden/web-vault@sha256:8e8405d252bb6ecc7d59d90e9ba9dde09f35c1b6858371274c67c3e0a6f14a80 as vault
|
||||||
|
|
||||||
########################## BUILD IMAGE ##########################
|
########################## BUILD IMAGE ##########################
|
||||||
FROM rust:1.61-bullseye as build
|
FROM rust:1.64-bullseye as build
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -123,7 +123,6 @@ RUN mkdir /data \
|
|||||||
openssl \
|
openssl \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
curl \
|
curl \
|
||||||
dumb-init \
|
|
||||||
libmariadb-dev-compat \
|
libmariadb-dev-compat \
|
||||||
libpq5 \
|
libpq5 \
|
||||||
&& apt-get clean \
|
&& apt-get clean \
|
||||||
@@ -152,10 +151,4 @@ COPY docker/start.sh /start.sh
|
|||||||
|
|
||||||
HEALTHCHECK --interval=60s --timeout=10s CMD ["/healthcheck.sh"]
|
HEALTHCHECK --interval=60s --timeout=10s CMD ["/healthcheck.sh"]
|
||||||
|
|
||||||
# Configures the startup!
|
|
||||||
# We should be able to remove the dumb-init now with Rocket 0.5
|
|
||||||
# But the balenalib images have some issues with there entry.sh
|
|
||||||
# See: https://github.com/balena-io-library/base-images/issues/735
|
|
||||||
# Lets keep using dumb-init for now, since that is working fine.
|
|
||||||
ENTRYPOINT ["/usr/bin/dumb-init", "--"]
|
|
||||||
CMD ["/start.sh"]
|
CMD ["/start.sh"]
|
||||||
|
@@ -16,18 +16,18 @@
|
|||||||
# - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
|
# - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
|
||||||
# click the tag name to view the digest of the image it currently points to.
|
# click the tag name to view the digest of the image it currently points to.
|
||||||
# - From the command line:
|
# - From the command line:
|
||||||
# $ docker pull vaultwarden/web-vault:v2022.6.2
|
# $ docker pull vaultwarden/web-vault:v2022.10.0
|
||||||
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2022.6.2
|
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2022.10.0
|
||||||
# [vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70]
|
# [vaultwarden/web-vault@sha256:8e8405d252bb6ecc7d59d90e9ba9dde09f35c1b6858371274c67c3e0a6f14a80]
|
||||||
#
|
#
|
||||||
# - Conversely, to get the tag name from the digest:
|
# - Conversely, to get the tag name from the digest:
|
||||||
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70
|
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:8e8405d252bb6ecc7d59d90e9ba9dde09f35c1b6858371274c67c3e0a6f14a80
|
||||||
# [vaultwarden/web-vault:v2022.6.2]
|
# [vaultwarden/web-vault:v2022.10.0]
|
||||||
#
|
#
|
||||||
FROM vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70 as vault
|
FROM vaultwarden/web-vault@sha256:8e8405d252bb6ecc7d59d90e9ba9dde09f35c1b6858371274c67c3e0a6f14a80 as vault
|
||||||
|
|
||||||
########################## BUILD IMAGE ##########################
|
########################## BUILD IMAGE ##########################
|
||||||
FROM blackdex/rust-musl:arm-musleabi-stable-1.61.0 as build
|
FROM blackdex/rust-musl:arm-musleabi-stable-1.64.0 as build
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -83,7 +83,7 @@ RUN cargo build --features ${DB} --release --target=arm-unknown-linux-musleabi
|
|||||||
######################## RUNTIME IMAGE ########################
|
######################## RUNTIME IMAGE ########################
|
||||||
# Create a new stage with a minimal image
|
# Create a new stage with a minimal image
|
||||||
# because we already have a binary built
|
# because we already have a binary built
|
||||||
FROM balenalib/rpi-alpine:3.15
|
FROM balenalib/rpi-alpine:3.16
|
||||||
|
|
||||||
ENV ROCKET_PROFILE="release" \
|
ENV ROCKET_PROFILE="release" \
|
||||||
ROCKET_ADDRESS=0.0.0.0 \
|
ROCKET_ADDRESS=0.0.0.0 \
|
||||||
@@ -100,7 +100,6 @@ RUN mkdir /data \
|
|||||||
openssl \
|
openssl \
|
||||||
tzdata \
|
tzdata \
|
||||||
curl \
|
curl \
|
||||||
dumb-init \
|
|
||||||
ca-certificates
|
ca-certificates
|
||||||
|
|
||||||
# hadolint ignore=DL3059
|
# hadolint ignore=DL3059
|
||||||
@@ -121,10 +120,4 @@ COPY docker/start.sh /start.sh
|
|||||||
|
|
||||||
HEALTHCHECK --interval=60s --timeout=10s CMD ["/healthcheck.sh"]
|
HEALTHCHECK --interval=60s --timeout=10s CMD ["/healthcheck.sh"]
|
||||||
|
|
||||||
# Configures the startup!
|
|
||||||
# We should be able to remove the dumb-init now with Rocket 0.5
|
|
||||||
# But the balenalib images have some issues with there entry.sh
|
|
||||||
# See: https://github.com/balena-io-library/base-images/issues/735
|
|
||||||
# Lets keep using dumb-init for now, since that is working fine.
|
|
||||||
ENTRYPOINT ["/usr/bin/dumb-init", "--"]
|
|
||||||
CMD ["/start.sh"]
|
CMD ["/start.sh"]
|
||||||
|
@@ -16,18 +16,18 @@
|
|||||||
# - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
|
# - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
|
||||||
# click the tag name to view the digest of the image it currently points to.
|
# click the tag name to view the digest of the image it currently points to.
|
||||||
# - From the command line:
|
# - From the command line:
|
||||||
# $ docker pull vaultwarden/web-vault:v2022.6.2
|
# $ docker pull vaultwarden/web-vault:v2022.10.0
|
||||||
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2022.6.2
|
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2022.10.0
|
||||||
# [vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70]
|
# [vaultwarden/web-vault@sha256:8e8405d252bb6ecc7d59d90e9ba9dde09f35c1b6858371274c67c3e0a6f14a80]
|
||||||
#
|
#
|
||||||
# - Conversely, to get the tag name from the digest:
|
# - Conversely, to get the tag name from the digest:
|
||||||
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70
|
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:8e8405d252bb6ecc7d59d90e9ba9dde09f35c1b6858371274c67c3e0a6f14a80
|
||||||
# [vaultwarden/web-vault:v2022.6.2]
|
# [vaultwarden/web-vault:v2022.10.0]
|
||||||
#
|
#
|
||||||
FROM vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70 as vault
|
FROM vaultwarden/web-vault@sha256:8e8405d252bb6ecc7d59d90e9ba9dde09f35c1b6858371274c67c3e0a6f14a80 as vault
|
||||||
|
|
||||||
########################## BUILD IMAGE ##########################
|
########################## BUILD IMAGE ##########################
|
||||||
FROM rust:1.61-bullseye as build
|
FROM rust:1.64-bullseye as build
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -123,7 +123,6 @@ RUN mkdir /data \
|
|||||||
openssl \
|
openssl \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
curl \
|
curl \
|
||||||
dumb-init \
|
|
||||||
libmariadb-dev-compat \
|
libmariadb-dev-compat \
|
||||||
libpq5 \
|
libpq5 \
|
||||||
&& apt-get clean \
|
&& apt-get clean \
|
||||||
@@ -152,10 +151,4 @@ COPY docker/start.sh /start.sh
|
|||||||
|
|
||||||
HEALTHCHECK --interval=60s --timeout=10s CMD ["/healthcheck.sh"]
|
HEALTHCHECK --interval=60s --timeout=10s CMD ["/healthcheck.sh"]
|
||||||
|
|
||||||
# Configures the startup!
|
|
||||||
# We should be able to remove the dumb-init now with Rocket 0.5
|
|
||||||
# But the balenalib images have some issues with there entry.sh
|
|
||||||
# See: https://github.com/balena-io-library/base-images/issues/735
|
|
||||||
# Lets keep using dumb-init for now, since that is working fine.
|
|
||||||
ENTRYPOINT ["/usr/bin/dumb-init", "--"]
|
|
||||||
CMD ["/start.sh"]
|
CMD ["/start.sh"]
|
||||||
|
@@ -16,18 +16,18 @@
|
|||||||
# - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
|
# - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
|
||||||
# click the tag name to view the digest of the image it currently points to.
|
# click the tag name to view the digest of the image it currently points to.
|
||||||
# - From the command line:
|
# - From the command line:
|
||||||
# $ docker pull vaultwarden/web-vault:v2022.6.2
|
# $ docker pull vaultwarden/web-vault:v2022.10.0
|
||||||
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2022.6.2
|
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2022.10.0
|
||||||
# [vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70]
|
# [vaultwarden/web-vault@sha256:8e8405d252bb6ecc7d59d90e9ba9dde09f35c1b6858371274c67c3e0a6f14a80]
|
||||||
#
|
#
|
||||||
# - Conversely, to get the tag name from the digest:
|
# - Conversely, to get the tag name from the digest:
|
||||||
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70
|
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:8e8405d252bb6ecc7d59d90e9ba9dde09f35c1b6858371274c67c3e0a6f14a80
|
||||||
# [vaultwarden/web-vault:v2022.6.2]
|
# [vaultwarden/web-vault:v2022.10.0]
|
||||||
#
|
#
|
||||||
FROM vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70 as vault
|
FROM vaultwarden/web-vault@sha256:8e8405d252bb6ecc7d59d90e9ba9dde09f35c1b6858371274c67c3e0a6f14a80 as vault
|
||||||
|
|
||||||
########################## BUILD IMAGE ##########################
|
########################## BUILD IMAGE ##########################
|
||||||
FROM blackdex/rust-musl:arm-musleabi-stable-1.61.0 as build
|
FROM blackdex/rust-musl:arm-musleabi-stable-1.64.0 as build
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -83,7 +83,7 @@ RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/.
|
|||||||
######################## RUNTIME IMAGE ########################
|
######################## RUNTIME IMAGE ########################
|
||||||
# Create a new stage with a minimal image
|
# Create a new stage with a minimal image
|
||||||
# because we already have a binary built
|
# because we already have a binary built
|
||||||
FROM balenalib/rpi-alpine:3.15
|
FROM balenalib/rpi-alpine:3.16
|
||||||
|
|
||||||
ENV ROCKET_PROFILE="release" \
|
ENV ROCKET_PROFILE="release" \
|
||||||
ROCKET_ADDRESS=0.0.0.0 \
|
ROCKET_ADDRESS=0.0.0.0 \
|
||||||
@@ -100,7 +100,6 @@ RUN mkdir /data \
|
|||||||
openssl \
|
openssl \
|
||||||
tzdata \
|
tzdata \
|
||||||
curl \
|
curl \
|
||||||
dumb-init \
|
|
||||||
ca-certificates
|
ca-certificates
|
||||||
|
|
||||||
# hadolint ignore=DL3059
|
# hadolint ignore=DL3059
|
||||||
@@ -121,10 +120,4 @@ COPY docker/start.sh /start.sh
|
|||||||
|
|
||||||
HEALTHCHECK --interval=60s --timeout=10s CMD ["/healthcheck.sh"]
|
HEALTHCHECK --interval=60s --timeout=10s CMD ["/healthcheck.sh"]
|
||||||
|
|
||||||
# Configures the startup!
|
|
||||||
# We should be able to remove the dumb-init now with Rocket 0.5
|
|
||||||
# But the balenalib images have some issues with there entry.sh
|
|
||||||
# See: https://github.com/balena-io-library/base-images/issues/735
|
|
||||||
# Lets keep using dumb-init for now, since that is working fine.
|
|
||||||
ENTRYPOINT ["/usr/bin/dumb-init", "--"]
|
|
||||||
CMD ["/start.sh"]
|
CMD ["/start.sh"]
|
||||||
|
@@ -16,18 +16,18 @@
|
|||||||
# - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
|
# - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
|
||||||
# click the tag name to view the digest of the image it currently points to.
|
# click the tag name to view the digest of the image it currently points to.
|
||||||
# - From the command line:
|
# - From the command line:
|
||||||
# $ docker pull vaultwarden/web-vault:v2022.6.2
|
# $ docker pull vaultwarden/web-vault:v2022.10.0
|
||||||
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2022.6.2
|
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2022.10.0
|
||||||
# [vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70]
|
# [vaultwarden/web-vault@sha256:8e8405d252bb6ecc7d59d90e9ba9dde09f35c1b6858371274c67c3e0a6f14a80]
|
||||||
#
|
#
|
||||||
# - Conversely, to get the tag name from the digest:
|
# - Conversely, to get the tag name from the digest:
|
||||||
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70
|
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:8e8405d252bb6ecc7d59d90e9ba9dde09f35c1b6858371274c67c3e0a6f14a80
|
||||||
# [vaultwarden/web-vault:v2022.6.2]
|
# [vaultwarden/web-vault:v2022.10.0]
|
||||||
#
|
#
|
||||||
FROM vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70 as vault
|
FROM vaultwarden/web-vault@sha256:8e8405d252bb6ecc7d59d90e9ba9dde09f35c1b6858371274c67c3e0a6f14a80 as vault
|
||||||
|
|
||||||
########################## BUILD IMAGE ##########################
|
########################## BUILD IMAGE ##########################
|
||||||
FROM rust:1.61-bullseye as build
|
FROM rust:1.64-bullseye as build
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -123,7 +123,6 @@ RUN mkdir /data \
|
|||||||
openssl \
|
openssl \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
curl \
|
curl \
|
||||||
dumb-init \
|
|
||||||
libmariadb-dev-compat \
|
libmariadb-dev-compat \
|
||||||
libpq5 \
|
libpq5 \
|
||||||
&& apt-get clean \
|
&& apt-get clean \
|
||||||
@@ -147,10 +146,4 @@ COPY docker/start.sh /start.sh
|
|||||||
|
|
||||||
HEALTHCHECK --interval=60s --timeout=10s CMD ["/healthcheck.sh"]
|
HEALTHCHECK --interval=60s --timeout=10s CMD ["/healthcheck.sh"]
|
||||||
|
|
||||||
# Configures the startup!
|
|
||||||
# We should be able to remove the dumb-init now with Rocket 0.5
|
|
||||||
# But the balenalib images have some issues with there entry.sh
|
|
||||||
# See: https://github.com/balena-io-library/base-images/issues/735
|
|
||||||
# Lets keep using dumb-init for now, since that is working fine.
|
|
||||||
ENTRYPOINT ["/usr/bin/dumb-init", "--"]
|
|
||||||
CMD ["/start.sh"]
|
CMD ["/start.sh"]
|
||||||
|
@@ -16,18 +16,18 @@
|
|||||||
# - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
|
# - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
|
||||||
# click the tag name to view the digest of the image it currently points to.
|
# click the tag name to view the digest of the image it currently points to.
|
||||||
# - From the command line:
|
# - From the command line:
|
||||||
# $ docker pull vaultwarden/web-vault:v2022.6.2
|
# $ docker pull vaultwarden/web-vault:v2022.10.0
|
||||||
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2022.6.2
|
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2022.10.0
|
||||||
# [vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70]
|
# [vaultwarden/web-vault@sha256:8e8405d252bb6ecc7d59d90e9ba9dde09f35c1b6858371274c67c3e0a6f14a80]
|
||||||
#
|
#
|
||||||
# - Conversely, to get the tag name from the digest:
|
# - Conversely, to get the tag name from the digest:
|
||||||
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70
|
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:8e8405d252bb6ecc7d59d90e9ba9dde09f35c1b6858371274c67c3e0a6f14a80
|
||||||
# [vaultwarden/web-vault:v2022.6.2]
|
# [vaultwarden/web-vault:v2022.10.0]
|
||||||
#
|
#
|
||||||
FROM vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70 as vault
|
FROM vaultwarden/web-vault@sha256:8e8405d252bb6ecc7d59d90e9ba9dde09f35c1b6858371274c67c3e0a6f14a80 as vault
|
||||||
|
|
||||||
########################## BUILD IMAGE ##########################
|
########################## BUILD IMAGE ##########################
|
||||||
FROM blackdex/rust-musl:armv7-musleabihf-stable-1.61.0 as build
|
FROM blackdex/rust-musl:armv7-musleabihf-stable-1.64.0 as build
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -81,7 +81,7 @@ RUN cargo build --features ${DB} --release --target=armv7-unknown-linux-musleabi
|
|||||||
######################## RUNTIME IMAGE ########################
|
######################## RUNTIME IMAGE ########################
|
||||||
# Create a new stage with a minimal image
|
# Create a new stage with a minimal image
|
||||||
# because we already have a binary built
|
# because we already have a binary built
|
||||||
FROM balenalib/armv7hf-alpine:3.15
|
FROM balenalib/armv7hf-alpine:3.16
|
||||||
|
|
||||||
ENV ROCKET_PROFILE="release" \
|
ENV ROCKET_PROFILE="release" \
|
||||||
ROCKET_ADDRESS=0.0.0.0 \
|
ROCKET_ADDRESS=0.0.0.0 \
|
||||||
@@ -98,7 +98,6 @@ RUN mkdir /data \
|
|||||||
openssl \
|
openssl \
|
||||||
tzdata \
|
tzdata \
|
||||||
curl \
|
curl \
|
||||||
dumb-init \
|
|
||||||
ca-certificates
|
ca-certificates
|
||||||
|
|
||||||
# hadolint ignore=DL3059
|
# hadolint ignore=DL3059
|
||||||
@@ -119,10 +118,4 @@ COPY docker/start.sh /start.sh
|
|||||||
|
|
||||||
HEALTHCHECK --interval=60s --timeout=10s CMD ["/healthcheck.sh"]
|
HEALTHCHECK --interval=60s --timeout=10s CMD ["/healthcheck.sh"]
|
||||||
|
|
||||||
# Configures the startup!
|
|
||||||
# We should be able to remove the dumb-init now with Rocket 0.5
|
|
||||||
# But the balenalib images have some issues with there entry.sh
|
|
||||||
# See: https://github.com/balena-io-library/base-images/issues/735
|
|
||||||
# Lets keep using dumb-init for now, since that is working fine.
|
|
||||||
ENTRYPOINT ["/usr/bin/dumb-init", "--"]
|
|
||||||
CMD ["/start.sh"]
|
CMD ["/start.sh"]
|
||||||
|
@@ -16,18 +16,18 @@
|
|||||||
# - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
|
# - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
|
||||||
# click the tag name to view the digest of the image it currently points to.
|
# click the tag name to view the digest of the image it currently points to.
|
||||||
# - From the command line:
|
# - From the command line:
|
||||||
# $ docker pull vaultwarden/web-vault:v2022.6.2
|
# $ docker pull vaultwarden/web-vault:v2022.10.0
|
||||||
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2022.6.2
|
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2022.10.0
|
||||||
# [vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70]
|
# [vaultwarden/web-vault@sha256:8e8405d252bb6ecc7d59d90e9ba9dde09f35c1b6858371274c67c3e0a6f14a80]
|
||||||
#
|
#
|
||||||
# - Conversely, to get the tag name from the digest:
|
# - Conversely, to get the tag name from the digest:
|
||||||
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70
|
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:8e8405d252bb6ecc7d59d90e9ba9dde09f35c1b6858371274c67c3e0a6f14a80
|
||||||
# [vaultwarden/web-vault:v2022.6.2]
|
# [vaultwarden/web-vault:v2022.10.0]
|
||||||
#
|
#
|
||||||
FROM vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70 as vault
|
FROM vaultwarden/web-vault@sha256:8e8405d252bb6ecc7d59d90e9ba9dde09f35c1b6858371274c67c3e0a6f14a80 as vault
|
||||||
|
|
||||||
########################## BUILD IMAGE ##########################
|
########################## BUILD IMAGE ##########################
|
||||||
FROM rust:1.61-bullseye as build
|
FROM rust:1.64-bullseye as build
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -123,7 +123,6 @@ RUN mkdir /data \
|
|||||||
openssl \
|
openssl \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
curl \
|
curl \
|
||||||
dumb-init \
|
|
||||||
libmariadb-dev-compat \
|
libmariadb-dev-compat \
|
||||||
libpq5 \
|
libpq5 \
|
||||||
&& apt-get clean \
|
&& apt-get clean \
|
||||||
@@ -147,10 +146,4 @@ COPY docker/start.sh /start.sh
|
|||||||
|
|
||||||
HEALTHCHECK --interval=60s --timeout=10s CMD ["/healthcheck.sh"]
|
HEALTHCHECK --interval=60s --timeout=10s CMD ["/healthcheck.sh"]
|
||||||
|
|
||||||
# Configures the startup!
|
|
||||||
# We should be able to remove the dumb-init now with Rocket 0.5
|
|
||||||
# But the balenalib images have some issues with there entry.sh
|
|
||||||
# See: https://github.com/balena-io-library/base-images/issues/735
|
|
||||||
# Lets keep using dumb-init for now, since that is working fine.
|
|
||||||
ENTRYPOINT ["/usr/bin/dumb-init", "--"]
|
|
||||||
CMD ["/start.sh"]
|
CMD ["/start.sh"]
|
||||||
|
@@ -16,18 +16,18 @@
|
|||||||
# - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
|
# - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
|
||||||
# click the tag name to view the digest of the image it currently points to.
|
# click the tag name to view the digest of the image it currently points to.
|
||||||
# - From the command line:
|
# - From the command line:
|
||||||
# $ docker pull vaultwarden/web-vault:v2022.6.2
|
# $ docker pull vaultwarden/web-vault:v2022.10.0
|
||||||
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2022.6.2
|
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2022.10.0
|
||||||
# [vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70]
|
# [vaultwarden/web-vault@sha256:8e8405d252bb6ecc7d59d90e9ba9dde09f35c1b6858371274c67c3e0a6f14a80]
|
||||||
#
|
#
|
||||||
# - Conversely, to get the tag name from the digest:
|
# - Conversely, to get the tag name from the digest:
|
||||||
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70
|
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:8e8405d252bb6ecc7d59d90e9ba9dde09f35c1b6858371274c67c3e0a6f14a80
|
||||||
# [vaultwarden/web-vault:v2022.6.2]
|
# [vaultwarden/web-vault:v2022.10.0]
|
||||||
#
|
#
|
||||||
FROM vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70 as vault
|
FROM vaultwarden/web-vault@sha256:8e8405d252bb6ecc7d59d90e9ba9dde09f35c1b6858371274c67c3e0a6f14a80 as vault
|
||||||
|
|
||||||
########################## BUILD IMAGE ##########################
|
########################## BUILD IMAGE ##########################
|
||||||
FROM blackdex/rust-musl:armv7-musleabihf-stable-1.61.0 as build
|
FROM blackdex/rust-musl:armv7-musleabihf-stable-1.64.0 as build
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -81,7 +81,7 @@ RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/.
|
|||||||
######################## RUNTIME IMAGE ########################
|
######################## RUNTIME IMAGE ########################
|
||||||
# Create a new stage with a minimal image
|
# Create a new stage with a minimal image
|
||||||
# because we already have a binary built
|
# because we already have a binary built
|
||||||
FROM balenalib/armv7hf-alpine:3.15
|
FROM balenalib/armv7hf-alpine:3.16
|
||||||
|
|
||||||
ENV ROCKET_PROFILE="release" \
|
ENV ROCKET_PROFILE="release" \
|
||||||
ROCKET_ADDRESS=0.0.0.0 \
|
ROCKET_ADDRESS=0.0.0.0 \
|
||||||
@@ -98,7 +98,6 @@ RUN mkdir /data \
|
|||||||
openssl \
|
openssl \
|
||||||
tzdata \
|
tzdata \
|
||||||
curl \
|
curl \
|
||||||
dumb-init \
|
|
||||||
ca-certificates
|
ca-certificates
|
||||||
|
|
||||||
# hadolint ignore=DL3059
|
# hadolint ignore=DL3059
|
||||||
@@ -119,10 +118,4 @@ COPY docker/start.sh /start.sh
|
|||||||
|
|
||||||
HEALTHCHECK --interval=60s --timeout=10s CMD ["/healthcheck.sh"]
|
HEALTHCHECK --interval=60s --timeout=10s CMD ["/healthcheck.sh"]
|
||||||
|
|
||||||
# Configures the startup!
|
|
||||||
# We should be able to remove the dumb-init now with Rocket 0.5
|
|
||||||
# But the balenalib images have some issues with there entry.sh
|
|
||||||
# See: https://github.com/balena-io-library/base-images/issues/735
|
|
||||||
# Lets keep using dumb-init for now, since that is working fine.
|
|
||||||
ENTRYPOINT ["/usr/bin/dumb-init", "--"]
|
|
||||||
CMD ["/start.sh"]
|
CMD ["/start.sh"]
|
||||||
|
@@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
# Use the value of the corresponding env var (if present),
|
# Use the value of the corresponding env var (if present),
|
||||||
# or a default value otherwise.
|
# or a default value otherwise.
|
||||||
: ${DATA_FOLDER:="data"}
|
: "${DATA_FOLDER:="data"}"
|
||||||
: ${ROCKET_PORT:="80"}
|
: "${ROCKET_PORT:="80"}"
|
||||||
|
|
||||||
CONFIG_FILE="${DATA_FOLDER}"/config.json
|
CONFIG_FILE="${DATA_FOLDER}"/config.json
|
||||||
|
|
||||||
|
@@ -9,15 +9,15 @@ fi
|
|||||||
|
|
||||||
if [ -d /etc/vaultwarden.d ]; then
|
if [ -d /etc/vaultwarden.d ]; then
|
||||||
for f in /etc/vaultwarden.d/*.sh; do
|
for f in /etc/vaultwarden.d/*.sh; do
|
||||||
if [ -r $f ]; then
|
if [ -r "${f}" ]; then
|
||||||
. $f
|
. "${f}"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
elif [ -d /etc/bitwarden_rs.d ]; then
|
elif [ -d /etc/bitwarden_rs.d ]; then
|
||||||
echo "### You are using the old /etc/bitwarden_rs.d script directory, please migrate to /etc/vaultwarden.d ###"
|
echo "### You are using the old /etc/bitwarden_rs.d script directory, please migrate to /etc/vaultwarden.d ###"
|
||||||
for f in /etc/bitwarden_rs.d/*.sh; do
|
for f in /etc/bitwarden_rs.d/*.sh; do
|
||||||
if [ -r $f ]; then
|
if [ -r "${f}" ]; then
|
||||||
. $f
|
. "${f}"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
@@ -1 +1 @@
|
|||||||
1.61.0
|
1.64.0
|
||||||
|
@@ -7,8 +7,8 @@ use rocket::serde::json::Json;
|
|||||||
use rocket::{
|
use rocket::{
|
||||||
form::Form,
|
form::Form,
|
||||||
http::{Cookie, CookieJar, SameSite, Status},
|
http::{Cookie, CookieJar, SameSite, Status},
|
||||||
request::{self, FlashMessage, FromRequest, Outcome, Request},
|
request::{self, FromRequest, Outcome, Request},
|
||||||
response::{content::RawHtml as Html, Flash, Redirect},
|
response::{content::RawHtml as Html, Redirect},
|
||||||
Route,
|
Route,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -141,10 +141,24 @@ fn admin_url(referer: Referer) -> String {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Responder)]
|
||||||
|
enum AdminResponse {
|
||||||
|
#[response(status = 200)]
|
||||||
|
Ok(ApiResult<Html<String>>),
|
||||||
|
#[response(status = 401)]
|
||||||
|
Unauthorized(ApiResult<Html<String>>),
|
||||||
|
#[response(status = 429)]
|
||||||
|
TooManyRequests(ApiResult<Html<String>>),
|
||||||
|
}
|
||||||
|
|
||||||
#[get("/", rank = 2)]
|
#[get("/", rank = 2)]
|
||||||
fn admin_login(flash: Option<FlashMessage<'_>>) -> ApiResult<Html<String>> {
|
fn admin_login() -> ApiResult<Html<String>> {
|
||||||
|
render_admin_login(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_admin_login(msg: Option<&str>) -> ApiResult<Html<String>> {
|
||||||
// If there is an error, show it
|
// If there is an error, show it
|
||||||
let msg = flash.map(|msg| format!("{}: {}", msg.kind(), msg.message()));
|
let msg = msg.map(|msg| format!("Error: {msg}"));
|
||||||
let json = json!({
|
let json = json!({
|
||||||
"page_content": "admin/login",
|
"page_content": "admin/login",
|
||||||
"version": VERSION,
|
"version": VERSION,
|
||||||
@@ -163,22 +177,17 @@ struct LoginForm {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[post("/", data = "<data>")]
|
#[post("/", data = "<data>")]
|
||||||
fn post_admin_login(
|
fn post_admin_login(data: Form<LoginForm>, cookies: &CookieJar<'_>, ip: ClientIp) -> AdminResponse {
|
||||||
data: Form<LoginForm>,
|
|
||||||
cookies: &CookieJar<'_>,
|
|
||||||
ip: ClientIp,
|
|
||||||
referer: Referer,
|
|
||||||
) -> Result<Redirect, Flash<Redirect>> {
|
|
||||||
let data = data.into_inner();
|
let data = data.into_inner();
|
||||||
|
|
||||||
if crate::ratelimit::check_limit_admin(&ip.ip).is_err() {
|
if crate::ratelimit::check_limit_admin(&ip.ip).is_err() {
|
||||||
return Err(Flash::error(Redirect::to(admin_url(referer)), "Too many requests, try again later."));
|
return AdminResponse::TooManyRequests(render_admin_login(Some("Too many requests, try again later.")));
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the token is invalid, redirect to login page
|
// If the token is invalid, redirect to login page
|
||||||
if !_validate_token(&data.token) {
|
if !_validate_token(&data.token) {
|
||||||
error!("Invalid admin token. IP: {}", ip.ip);
|
error!("Invalid admin token. IP: {}", ip.ip);
|
||||||
Err(Flash::error(Redirect::to(admin_url(referer)), "Invalid admin token, please try again."))
|
AdminResponse::Unauthorized(render_admin_login(Some("Invalid admin token, please try again.")))
|
||||||
} else {
|
} else {
|
||||||
// If the token received is valid, generate JWT and save it as a cookie
|
// If the token received is valid, generate JWT and save it as a cookie
|
||||||
let claims = generate_admin_claims();
|
let claims = generate_admin_claims();
|
||||||
@@ -192,7 +201,7 @@ fn post_admin_login(
|
|||||||
.finish();
|
.finish();
|
||||||
|
|
||||||
cookies.add(cookie);
|
cookies.add(cookie);
|
||||||
Ok(Redirect::to(admin_url(referer)))
|
AdminResponse::Ok(render_admin_page())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -244,12 +253,16 @@ impl AdminTemplateData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/", rank = 1)]
|
fn render_admin_page() -> ApiResult<Html<String>> {
|
||||||
fn admin_page(_token: AdminToken) -> ApiResult<Html<String>> {
|
|
||||||
let text = AdminTemplateData::new().render()?;
|
let text = AdminTemplateData::new().render()?;
|
||||||
Ok(Html(text))
|
Ok(Html(text))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[get("/", rank = 1)]
|
||||||
|
fn admin_page(_token: AdminToken) -> ApiResult<Html<String>> {
|
||||||
|
render_admin_page()
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
struct InviteData {
|
struct InviteData {
|
||||||
@@ -303,7 +316,7 @@ async fn test_smtp(data: Json<InviteData>, _token: AdminToken) -> EmptyResult {
|
|||||||
#[get("/logout")]
|
#[get("/logout")]
|
||||||
fn logout(cookies: &CookieJar<'_>, referer: Referer) -> Redirect {
|
fn logout(cookies: &CookieJar<'_>, referer: Referer) -> Redirect {
|
||||||
cookies.remove(Cookie::build(COOKIE_NAME, "").path(admin_path()).finish());
|
cookies.remove(Cookie::build(COOKIE_NAME, "").path(admin_path()).finish());
|
||||||
Redirect::to(admin_url(referer))
|
Redirect::temporary(admin_url(referer))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/users")]
|
#[get("/users")]
|
||||||
@@ -418,15 +431,26 @@ async fn update_user_org_type(data: Json<UserOrgTypeData>, _token: AdminToken, c
|
|||||||
};
|
};
|
||||||
|
|
||||||
if user_to_edit.atype == UserOrgType::Owner && new_type != UserOrgType::Owner {
|
if user_to_edit.atype == UserOrgType::Owner && new_type != UserOrgType::Owner {
|
||||||
// Removing owner permmission, check that there are at least another owner
|
// Removing owner permmission, check that there is at least one other confirmed owner
|
||||||
let num_owners =
|
if UserOrganization::count_confirmed_by_org_and_type(&data.org_uuid, UserOrgType::Owner, &conn).await <= 1 {
|
||||||
UserOrganization::find_by_org_and_type(&data.org_uuid, UserOrgType::Owner as i32, &conn).await.len();
|
|
||||||
|
|
||||||
if num_owners <= 1 {
|
|
||||||
err!("Can't change the type of the last owner")
|
err!("Can't change the type of the last owner")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This check is also done at api::organizations::{accept_invite(), _confirm_invite, _activate_user(), edit_user()}, update_user_org_type
|
||||||
|
// It returns different error messages per function.
|
||||||
|
if new_type < UserOrgType::Admin {
|
||||||
|
match OrgPolicy::is_user_allowed(&user_to_edit.user_uuid, &user_to_edit.org_uuid, true, &conn).await {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(OrgPolicyErr::TwoFactorMissing) => {
|
||||||
|
err!("You cannot modify this user to this type because it has no two-step login method activated");
|
||||||
|
}
|
||||||
|
Err(OrgPolicyErr::SingleOrgEnforced) => {
|
||||||
|
err!("You cannot modify this user to this type because it is a member of an organization which forbids it");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
user_to_edit.atype = new_type;
|
user_to_edit.atype = new_type;
|
||||||
user_to_edit.save(&conn).await
|
user_to_edit.save(&conn).await
|
||||||
}
|
}
|
||||||
@@ -498,7 +522,6 @@ use cached::proc_macro::cached;
|
|||||||
async fn get_release_info(has_http_access: bool, running_within_docker: bool) -> (String, String, String) {
|
async fn get_release_info(has_http_access: bool, running_within_docker: bool) -> (String, String, String) {
|
||||||
// If the HTTP Check failed, do not even attempt to check for new versions since we were not able to connect with github.com anyway.
|
// If the HTTP Check failed, do not even attempt to check for new versions since we were not able to connect with github.com anyway.
|
||||||
if has_http_access {
|
if has_http_access {
|
||||||
info!("Running get_release_info!!");
|
|
||||||
(
|
(
|
||||||
match get_github_api::<GitRelease>("https://api.github.com/repos/dani-garcia/vaultwarden/releases/latest")
|
match get_github_api::<GitRelease>("https://api.github.com/repos/dani-garcia/vaultwarden/releases/latest")
|
||||||
.await
|
.await
|
||||||
|
@@ -81,7 +81,7 @@ fn enforce_password_hint_setting(password_hint: &Option<String>) -> EmptyResult
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[post("/accounts/register", data = "<data>")]
|
#[post("/accounts/register", data = "<data>")]
|
||||||
async fn register(data: JsonUpcase<RegisterData>, conn: DbConn) -> EmptyResult {
|
async fn register(data: JsonUpcase<RegisterData>, conn: DbConn) -> JsonResult {
|
||||||
let data: RegisterData = data.into_inner().data;
|
let data: RegisterData = data.into_inner().data;
|
||||||
let email = data.Email.to_lowercase();
|
let email = data.Email.to_lowercase();
|
||||||
|
|
||||||
@@ -178,7 +178,11 @@ async fn register(data: JsonUpcase<RegisterData>, conn: DbConn) -> EmptyResult {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
user.save(&conn).await
|
user.save(&conn).await?;
|
||||||
|
Ok(Json(json!({
|
||||||
|
"Object": "register",
|
||||||
|
"CaptchaBypassToken": "",
|
||||||
|
})))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/accounts/profile")]
|
#[get("/accounts/profile")]
|
||||||
|
@@ -328,7 +328,7 @@ async fn enforce_personal_ownership_policy(data: Option<&CipherData>, headers: &
|
|||||||
if data.is_none() || data.unwrap().OrganizationId.is_none() {
|
if data.is_none() || data.unwrap().OrganizationId.is_none() {
|
||||||
let user_uuid = &headers.user.uuid;
|
let user_uuid = &headers.user.uuid;
|
||||||
let policy_type = OrgPolicyType::PersonalOwnership;
|
let policy_type = OrgPolicyType::PersonalOwnership;
|
||||||
if OrgPolicy::is_applicable_to_user(user_uuid, policy_type, conn).await {
|
if OrgPolicy::is_applicable_to_user(user_uuid, policy_type, None, conn).await {
|
||||||
err!("Due to an Enterprise Policy, you are restricted from saving items to your personal vault.")
|
err!("Due to an Enterprise Policy, you are restricted from saving items to your personal vault.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -947,10 +947,12 @@ async fn save_attachment(
|
|||||||
|
|
||||||
let mut data = data.into_inner();
|
let mut data = data.into_inner();
|
||||||
|
|
||||||
// There seems to be a bug somewhere regarding uploading attachments using the Android Client (Maybe iOS too?)
|
// There is a bug regarding uploading attachments/sends using the Mobile clients
|
||||||
// See: https://github.com/dani-garcia/vaultwarden/issues/2644
|
// See: https://github.com/dani-garcia/vaultwarden/issues/2644 && https://github.com/bitwarden/mobile/issues/2018
|
||||||
// Since all other clients seem to match TempFile::File and not TempFile::Buffered lets catch this and return an error for now.
|
// This has been fixed via a PR: https://github.com/bitwarden/mobile/pull/2031, but hasn't landed in a new release yet.
|
||||||
// We need to figure out how to solve this, but for now it's better to not accept these attachments since they will be broken.
|
// On the vaultwarden side this is temporarily fixed by using a custom multer library
|
||||||
|
// See: https://github.com/dani-garcia/vaultwarden/pull/2675
|
||||||
|
// In any case we will match TempFile::File and not TempFile::Buffered, since Buffered will alter the contents.
|
||||||
if let TempFile::Buffered {
|
if let TempFile::Buffered {
|
||||||
content: _,
|
content: _,
|
||||||
} = &data.data
|
} = &data.data
|
||||||
|
@@ -258,7 +258,7 @@ async fn send_invite(data: JsonUpcase<EmergencyAccessInviteData>, headers: Heade
|
|||||||
match User::find_by_mail(&email, &conn).await {
|
match User::find_by_mail(&email, &conn).await {
|
||||||
Some(user) => {
|
Some(user) => {
|
||||||
match accept_invite_process(user.uuid, new_emergency_access.uuid, Some(email), conn.borrow()).await {
|
match accept_invite_process(user.uuid, new_emergency_access.uuid, Some(email), conn.borrow()).await {
|
||||||
Ok(v) => (v),
|
Ok(v) => v,
|
||||||
Err(e) => err!(e.to_string()),
|
Err(e) => err!(e.to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -317,7 +317,7 @@ async fn resend_invite(emer_id: String, headers: Headers, conn: DbConn) -> Empty
|
|||||||
match accept_invite_process(grantee_user.uuid, emergency_access.uuid, emergency_access.email, conn.borrow())
|
match accept_invite_process(grantee_user.uuid, emergency_access.uuid, emergency_access.email, conn.borrow())
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(v) => (v),
|
Ok(v) => v,
|
||||||
Err(e) => err!(e.to_string()),
|
Err(e) => err!(e.to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -363,7 +363,7 @@ async fn accept_invite(emer_id: String, data: JsonUpcase<AcceptData>, conn: DbCo
|
|||||||
&& (claims.grantor_email.is_some() && grantor_user.email == claims.grantor_email.unwrap())
|
&& (claims.grantor_email.is_some() && grantor_user.email == claims.grantor_email.unwrap())
|
||||||
{
|
{
|
||||||
match accept_invite_process(grantee_user.uuid.clone(), emer_id, Some(grantee_user.email.clone()), &conn).await {
|
match accept_invite_process(grantee_user.uuid.clone(), emer_id, Some(grantee_user.email.clone()), &conn).await {
|
||||||
Ok(v) => (v),
|
Ok(v) => v,
|
||||||
Err(e) => err!(e.to_string()),
|
Err(e) => err!(e.to_string()),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -16,7 +16,7 @@ pub fn routes() -> Vec<Route> {
|
|||||||
let mut device_token_routes = routes![clear_device_token, put_device_token];
|
let mut device_token_routes = routes![clear_device_token, put_device_token];
|
||||||
let mut eq_domains_routes = routes![get_eq_domains, post_eq_domains, put_eq_domains];
|
let mut eq_domains_routes = routes![get_eq_domains, post_eq_domains, put_eq_domains];
|
||||||
let mut hibp_routes = routes![hibp_breach];
|
let mut hibp_routes = routes![hibp_breach];
|
||||||
let mut meta_routes = routes![alive, now, version];
|
let mut meta_routes = routes![alive, now, version, config];
|
||||||
|
|
||||||
let mut routes = Vec::new();
|
let mut routes = Vec::new();
|
||||||
routes.append(&mut accounts::routes());
|
routes.append(&mut accounts::routes());
|
||||||
@@ -38,6 +38,7 @@ pub fn routes() -> Vec<Route> {
|
|||||||
// Move this somewhere else
|
// Move this somewhere else
|
||||||
//
|
//
|
||||||
use rocket::serde::json::Json;
|
use rocket::serde::json::Json;
|
||||||
|
use rocket::Catcher;
|
||||||
use rocket::Route;
|
use rocket::Route;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
@@ -200,3 +201,38 @@ pub fn now() -> Json<String> {
|
|||||||
fn version() -> Json<&'static str> {
|
fn version() -> Json<&'static str> {
|
||||||
Json(crate::VERSION.unwrap_or_default())
|
Json(crate::VERSION.unwrap_or_default())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[get("/config")]
|
||||||
|
fn config() -> Json<Value> {
|
||||||
|
let domain = crate::CONFIG.domain();
|
||||||
|
Json(json!({
|
||||||
|
"version": crate::VERSION,
|
||||||
|
"gitHash": option_env!("GIT_REV"),
|
||||||
|
"server": {
|
||||||
|
"name": "Vaultwarden",
|
||||||
|
"url": "https://github.com/dani-garcia/vaultwarden"
|
||||||
|
},
|
||||||
|
"environment": {
|
||||||
|
"vault": domain,
|
||||||
|
"api": format!("{domain}/api"),
|
||||||
|
"identity": format!("{domain}/identity"),
|
||||||
|
"notifications": format!("{domain}/notifications"),
|
||||||
|
"sso": "",
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn catchers() -> Vec<Catcher> {
|
||||||
|
catchers![api_not_found]
|
||||||
|
}
|
||||||
|
|
||||||
|
#[catch(404)]
|
||||||
|
fn api_not_found() -> Json<Value> {
|
||||||
|
Json(json!({
|
||||||
|
"error": {
|
||||||
|
"code": 404,
|
||||||
|
"reason": "Not Found",
|
||||||
|
"description": "The requested resource could not be found."
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
@@ -10,7 +10,9 @@ use crate::{
|
|||||||
},
|
},
|
||||||
auth::{decode_invite, AdminHeaders, Headers, ManagerHeaders, ManagerHeadersLoose, OwnerHeaders},
|
auth::{decode_invite, AdminHeaders, Headers, ManagerHeaders, ManagerHeadersLoose, OwnerHeaders},
|
||||||
db::{models::*, DbConn},
|
db::{models::*, DbConn},
|
||||||
mail, CONFIG,
|
mail,
|
||||||
|
util::convert_json_key_lcase_first,
|
||||||
|
CONFIG,
|
||||||
};
|
};
|
||||||
|
|
||||||
use futures::{stream, stream::StreamExt};
|
use futures::{stream, stream::StreamExt};
|
||||||
@@ -61,6 +63,15 @@ pub fn routes() -> Vec<Route> {
|
|||||||
import,
|
import,
|
||||||
post_org_keys,
|
post_org_keys,
|
||||||
bulk_public_keys,
|
bulk_public_keys,
|
||||||
|
deactivate_organization_user,
|
||||||
|
bulk_deactivate_organization_user,
|
||||||
|
revoke_organization_user,
|
||||||
|
bulk_revoke_organization_user,
|
||||||
|
activate_organization_user,
|
||||||
|
bulk_activate_organization_user,
|
||||||
|
restore_organization_user,
|
||||||
|
bulk_restore_organization_user,
|
||||||
|
get_org_export
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,7 +118,7 @@ async fn create_organization(headers: Headers, data: JsonUpcase<OrgData>, conn:
|
|||||||
if !CONFIG.is_org_creation_allowed(&headers.user.email) {
|
if !CONFIG.is_org_creation_allowed(&headers.user.email) {
|
||||||
err!("User not allowed to create organizations")
|
err!("User not allowed to create organizations")
|
||||||
}
|
}
|
||||||
if OrgPolicy::is_applicable_to_user(&headers.user.uuid, OrgPolicyType::SingleOrg, &conn).await {
|
if OrgPolicy::is_applicable_to_user(&headers.user.uuid, OrgPolicyType::SingleOrg, None, &conn).await {
|
||||||
err!(
|
err!(
|
||||||
"You may not create an organization. You belong to an organization which has a policy that prohibits you from being a member of any other organization."
|
"You may not create an organization. You belong to an organization which has a policy that prohibits you from being a member of any other organization."
|
||||||
)
|
)
|
||||||
@@ -172,13 +183,10 @@ async fn leave_organization(org_id: String, headers: Headers, conn: DbConn) -> E
|
|||||||
match UserOrganization::find_by_user_and_org(&headers.user.uuid, &org_id, &conn).await {
|
match UserOrganization::find_by_user_and_org(&headers.user.uuid, &org_id, &conn).await {
|
||||||
None => err!("User not part of organization"),
|
None => err!("User not part of organization"),
|
||||||
Some(user_org) => {
|
Some(user_org) => {
|
||||||
if user_org.atype == UserOrgType::Owner {
|
if user_org.atype == UserOrgType::Owner
|
||||||
let num_owners =
|
&& UserOrganization::count_confirmed_by_org_and_type(&org_id, UserOrgType::Owner, &conn).await <= 1
|
||||||
UserOrganization::find_by_org_and_type(&org_id, UserOrgType::Owner as i32, &conn).await.len();
|
{
|
||||||
|
err!("The last owner can't leave")
|
||||||
if num_owners <= 1 {
|
|
||||||
err!("The last owner can't leave")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
user_org.delete(&conn).await
|
user_org.delete(&conn).await
|
||||||
@@ -241,15 +249,19 @@ async fn get_user_collections(headers: Headers, conn: DbConn) -> Json<Value> {
|
|||||||
|
|
||||||
#[get("/organizations/<org_id>/collections")]
|
#[get("/organizations/<org_id>/collections")]
|
||||||
async fn get_org_collections(org_id: String, _headers: ManagerHeadersLoose, conn: DbConn) -> Json<Value> {
|
async fn get_org_collections(org_id: String, _headers: ManagerHeadersLoose, conn: DbConn) -> Json<Value> {
|
||||||
Json(json!({
|
Json(_get_org_collections(&org_id, &conn).await)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn _get_org_collections(org_id: &str, conn: &DbConn) -> Value {
|
||||||
|
json!({
|
||||||
"Data":
|
"Data":
|
||||||
Collection::find_by_organization(&org_id, &conn).await
|
Collection::find_by_organization(org_id, conn).await
|
||||||
.iter()
|
.iter()
|
||||||
.map(Collection::to_json)
|
.map(Collection::to_json)
|
||||||
.collect::<Value>(),
|
.collect::<Value>(),
|
||||||
"Object": "list",
|
"Object": "list",
|
||||||
"ContinuationToken": null,
|
"ContinuationToken": null,
|
||||||
}))
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/organizations/<org_id>/collections", data = "<data>")]
|
#[post("/organizations/<org_id>/collections", data = "<data>")]
|
||||||
@@ -486,22 +498,26 @@ struct OrgIdData {
|
|||||||
|
|
||||||
#[get("/ciphers/organization-details?<data..>")]
|
#[get("/ciphers/organization-details?<data..>")]
|
||||||
async fn get_org_details(data: OrgIdData, headers: Headers, conn: DbConn) -> Json<Value> {
|
async fn get_org_details(data: OrgIdData, headers: Headers, conn: DbConn) -> Json<Value> {
|
||||||
let ciphers = Cipher::find_by_org(&data.organization_id, &conn).await;
|
Json(_get_org_details(&data.organization_id, &headers.host, &headers.user.uuid, &conn).await)
|
||||||
let cipher_sync_data = CipherSyncData::new(&headers.user.uuid, &ciphers, CipherSyncType::Organization, &conn).await;
|
}
|
||||||
|
|
||||||
|
async fn _get_org_details(org_id: &str, host: &str, user_uuid: &str, conn: &DbConn) -> Value {
|
||||||
|
let ciphers = Cipher::find_by_org(org_id, conn).await;
|
||||||
|
let cipher_sync_data = CipherSyncData::new(user_uuid, &ciphers, CipherSyncType::Organization, conn).await;
|
||||||
|
|
||||||
let ciphers_json = stream::iter(ciphers)
|
let ciphers_json = stream::iter(ciphers)
|
||||||
.then(|c| async {
|
.then(|c| async {
|
||||||
let c = c; // Move out this single variable
|
let c = c; // Move out this single variable
|
||||||
c.to_json(&headers.host, &headers.user.uuid, Some(&cipher_sync_data), &conn).await
|
c.to_json(host, user_uuid, Some(&cipher_sync_data), conn).await
|
||||||
})
|
})
|
||||||
.collect::<Vec<Value>>()
|
.collect::<Vec<Value>>()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
Json(json!({
|
json!({
|
||||||
"Data": ciphers_json,
|
"Data": ciphers_json,
|
||||||
"Object": "list",
|
"Object": "list",
|
||||||
"ContinuationToken": null,
|
"ContinuationToken": null,
|
||||||
}))
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/organizations/<org_id>/users")]
|
#[get("/organizations/<org_id>/users")]
|
||||||
@@ -749,17 +765,16 @@ struct AcceptData {
|
|||||||
Token: String,
|
Token: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/organizations/<_org_id>/users/<_org_user_id>/accept", data = "<data>")]
|
#[post("/organizations/<org_id>/users/<_org_user_id>/accept", data = "<data>")]
|
||||||
async fn accept_invite(
|
async fn accept_invite(
|
||||||
_org_id: String,
|
org_id: String,
|
||||||
_org_user_id: String,
|
_org_user_id: String,
|
||||||
data: JsonUpcase<AcceptData>,
|
data: JsonUpcase<AcceptData>,
|
||||||
conn: DbConn,
|
conn: DbConn,
|
||||||
) -> EmptyResult {
|
) -> EmptyResult {
|
||||||
// The web-vault passes org_id and org_user_id in the URL, but we are just reading them from the JWT instead
|
// The web-vault passes org_id and org_user_id in the URL, but we are just reading them from the JWT instead
|
||||||
let data: AcceptData = data.into_inner().data;
|
let data: AcceptData = data.into_inner().data;
|
||||||
let token = &data.Token;
|
let claims = decode_invite(&data.Token)?;
|
||||||
let claims = decode_invite(token)?;
|
|
||||||
|
|
||||||
match User::find_by_mail(&claims.email, &conn).await {
|
match User::find_by_mail(&claims.email, &conn).await {
|
||||||
Some(_) => {
|
Some(_) => {
|
||||||
@@ -775,46 +790,20 @@ async fn accept_invite(
|
|||||||
err!("User already accepted the invitation")
|
err!("User already accepted the invitation")
|
||||||
}
|
}
|
||||||
|
|
||||||
let user_twofactor_disabled = TwoFactor::find_by_user(&user_org.user_uuid, &conn).await.is_empty();
|
// This check is also done at accept_invite(), _confirm_invite, _activate_user(), edit_user(), admin::update_user_org_type
|
||||||
|
// It returns different error messages per function.
|
||||||
let policy = OrgPolicyType::TwoFactorAuthentication as i32;
|
if user_org.atype < UserOrgType::Admin {
|
||||||
let org_twofactor_policy_enabled =
|
match OrgPolicy::is_user_allowed(&user_org.user_uuid, &org_id, false, &conn).await {
|
||||||
match OrgPolicy::find_by_org_and_type(&user_org.org_uuid, policy, &conn).await {
|
Ok(_) => {}
|
||||||
Some(p) => p.enabled,
|
Err(OrgPolicyErr::TwoFactorMissing) => {
|
||||||
None => false,
|
err!("You cannot join this organization until you enable two-step login on your user account");
|
||||||
};
|
}
|
||||||
|
Err(OrgPolicyErr::SingleOrgEnforced) => {
|
||||||
if org_twofactor_policy_enabled && user_twofactor_disabled {
|
err!("You cannot join this organization because you are a member of an organization which forbids it");
|
||||||
err!("You cannot join this organization until you enable two-step login on your user account.")
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Enforce Single Organization Policy of organization user is trying to join
|
|
||||||
let single_org_policy_enabled =
|
|
||||||
match OrgPolicy::find_by_org_and_type(&user_org.org_uuid, OrgPolicyType::SingleOrg as i32, &conn)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Some(p) => p.enabled,
|
|
||||||
None => false,
|
|
||||||
};
|
|
||||||
if single_org_policy_enabled && user_org.atype < UserOrgType::Admin {
|
|
||||||
let is_member_of_another_org = UserOrganization::find_any_state_by_user(&user_org.user_uuid, &conn)
|
|
||||||
.await
|
|
||||||
.into_iter()
|
|
||||||
.filter(|uo| uo.org_uuid != user_org.org_uuid)
|
|
||||||
.count()
|
|
||||||
> 1;
|
|
||||||
if is_member_of_another_org {
|
|
||||||
err!("You may not join this organization until you leave or remove all other organizations.")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enforce Single Organization Policy of other organizations user is a member of
|
|
||||||
if OrgPolicy::is_applicable_to_user(&user_org.user_uuid, OrgPolicyType::SingleOrg, &conn).await {
|
|
||||||
err!(
|
|
||||||
"You cannot join this organization because you are a member of an organization which forbids it"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
user_org.status = UserOrgStatus::Accepted as i32;
|
user_org.status = UserOrgStatus::Accepted as i32;
|
||||||
user_org.save(&conn).await?;
|
user_org.save(&conn).await?;
|
||||||
}
|
}
|
||||||
@@ -918,6 +907,20 @@ async fn _confirm_invite(
|
|||||||
err!("User in invalid state")
|
err!("User in invalid state")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This check is also done at accept_invite(), _confirm_invite, _activate_user(), edit_user(), admin::update_user_org_type
|
||||||
|
// It returns different error messages per function.
|
||||||
|
if user_to_confirm.atype < UserOrgType::Admin {
|
||||||
|
match OrgPolicy::is_user_allowed(&user_to_confirm.user_uuid, org_id, true, conn).await {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(OrgPolicyErr::TwoFactorMissing) => {
|
||||||
|
err!("You cannot confirm this user because it has no two-step login method activated");
|
||||||
|
}
|
||||||
|
Err(OrgPolicyErr::SingleOrgEnforced) => {
|
||||||
|
err!("You cannot confirm this user because it is a member of an organization which forbids it");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
user_to_confirm.status = UserOrgStatus::Confirmed as i32;
|
user_to_confirm.status = UserOrgStatus::Confirmed as i32;
|
||||||
user_to_confirm.akey = key.to_string();
|
user_to_confirm.akey = key.to_string();
|
||||||
|
|
||||||
@@ -996,15 +999,30 @@ async fn edit_user(
|
|||||||
err!("Only Owners can edit Owner users")
|
err!("Only Owners can edit Owner users")
|
||||||
}
|
}
|
||||||
|
|
||||||
if user_to_edit.atype == UserOrgType::Owner && new_type != UserOrgType::Owner {
|
if user_to_edit.atype == UserOrgType::Owner
|
||||||
// Removing owner permmission, check that there are at least another owner
|
&& new_type != UserOrgType::Owner
|
||||||
let num_owners = UserOrganization::find_by_org_and_type(&org_id, UserOrgType::Owner as i32, &conn).await.len();
|
&& user_to_edit.status == UserOrgStatus::Confirmed as i32
|
||||||
|
{
|
||||||
if num_owners <= 1 {
|
// Removing owner permission, check that there is at least one other confirmed owner
|
||||||
|
if UserOrganization::count_confirmed_by_org_and_type(&org_id, UserOrgType::Owner, &conn).await <= 1 {
|
||||||
err!("Can't delete the last owner")
|
err!("Can't delete the last owner")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This check is also done at accept_invite(), _confirm_invite, _activate_user(), edit_user(), admin::update_user_org_type
|
||||||
|
// It returns different error messages per function.
|
||||||
|
if new_type < UserOrgType::Admin {
|
||||||
|
match OrgPolicy::is_user_allowed(&user_to_edit.user_uuid, &org_id, true, &conn).await {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(OrgPolicyErr::TwoFactorMissing) => {
|
||||||
|
err!("You cannot modify this user to this type because it has no two-step login method activated");
|
||||||
|
}
|
||||||
|
Err(OrgPolicyErr::SingleOrgEnforced) => {
|
||||||
|
err!("You cannot modify this user to this type because it is a member of an organization which forbids it");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
user_to_edit.access_all = data.AccessAll;
|
user_to_edit.access_all = data.AccessAll;
|
||||||
user_to_edit.atype = new_type as i32;
|
user_to_edit.atype = new_type as i32;
|
||||||
|
|
||||||
@@ -1082,11 +1100,9 @@ async fn _delete_user(org_id: &str, org_user_id: &str, headers: &AdminHeaders, c
|
|||||||
err!("Only Owners can delete Admins or Owners")
|
err!("Only Owners can delete Admins or Owners")
|
||||||
}
|
}
|
||||||
|
|
||||||
if user_to_delete.atype == UserOrgType::Owner {
|
if user_to_delete.atype == UserOrgType::Owner && user_to_delete.status == UserOrgStatus::Confirmed as i32 {
|
||||||
// Removing owner, check that there are at least another owner
|
// Removing owner, check that there is at least one other confirmed owner
|
||||||
let num_owners = UserOrganization::find_by_org_and_type(org_id, UserOrgType::Owner as i32, conn).await.len();
|
if UserOrganization::count_confirmed_by_org_and_type(org_id, UserOrgType::Owner, conn).await <= 1 {
|
||||||
|
|
||||||
if num_owners <= 1 {
|
|
||||||
err!("Can't delete the last owner")
|
err!("Can't delete the last owner")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1255,7 +1271,7 @@ async fn get_policy(org_id: String, pol_type: i32, _headers: AdminHeaders, conn:
|
|||||||
None => err!("Invalid or unsupported policy type"),
|
None => err!("Invalid or unsupported policy type"),
|
||||||
};
|
};
|
||||||
|
|
||||||
let policy = match OrgPolicy::find_by_org_and_type(&org_id, pol_type, &conn).await {
|
let policy = match OrgPolicy::find_by_org_and_type(&org_id, pol_type_enum, &conn).await {
|
||||||
Some(p) => p,
|
Some(p) => p,
|
||||||
None => OrgPolicy::new(org_id, pol_type_enum, "{}".to_string()),
|
None => OrgPolicy::new(org_id, pol_type_enum, "{}".to_string()),
|
||||||
};
|
};
|
||||||
@@ -1283,15 +1299,16 @@ async fn put_policy(
|
|||||||
|
|
||||||
let pol_type_enum = match OrgPolicyType::from_i32(pol_type) {
|
let pol_type_enum = match OrgPolicyType::from_i32(pol_type) {
|
||||||
Some(pt) => pt,
|
Some(pt) => pt,
|
||||||
None => err!("Invalid policy type"),
|
None => err!("Invalid or unsupported policy type"),
|
||||||
};
|
};
|
||||||
|
|
||||||
// If enabling the TwoFactorAuthentication policy, remove this org's members that do have 2FA
|
// When enabling the TwoFactorAuthentication policy, remove this org's members that do have 2FA
|
||||||
if pol_type_enum == OrgPolicyType::TwoFactorAuthentication && data.enabled {
|
if pol_type_enum == OrgPolicyType::TwoFactorAuthentication && data.enabled {
|
||||||
for member in UserOrganization::find_by_org(&org_id, &conn).await.into_iter() {
|
for member in UserOrganization::find_by_org(&org_id, &conn).await.into_iter() {
|
||||||
let user_twofactor_disabled = TwoFactor::find_by_user(&member.user_uuid, &conn).await.is_empty();
|
let user_twofactor_disabled = TwoFactor::find_by_user(&member.user_uuid, &conn).await.is_empty();
|
||||||
|
|
||||||
// Policy only applies to non-Owner/non-Admin members who have accepted joining the org
|
// Policy only applies to non-Owner/non-Admin members who have accepted joining the org
|
||||||
|
// Invited users still need to accept the invite and will get an error when they try to accept the invite.
|
||||||
if user_twofactor_disabled
|
if user_twofactor_disabled
|
||||||
&& member.atype < UserOrgType::Admin
|
&& member.atype < UserOrgType::Admin
|
||||||
&& member.status != UserOrgStatus::Invited as i32
|
&& member.status != UserOrgStatus::Invited as i32
|
||||||
@@ -1307,33 +1324,29 @@ async fn put_policy(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If enabling the SingleOrg policy, remove this org's members that are members of other orgs
|
// When enabling the SingleOrg policy, remove this org's members that are members of other orgs
|
||||||
if pol_type_enum == OrgPolicyType::SingleOrg && data.enabled {
|
if pol_type_enum == OrgPolicyType::SingleOrg && data.enabled {
|
||||||
for member in UserOrganization::find_by_org(&org_id, &conn).await.into_iter() {
|
for member in UserOrganization::find_by_org(&org_id, &conn).await.into_iter() {
|
||||||
// Policy only applies to non-Owner/non-Admin members who have accepted joining the org
|
// Policy only applies to non-Owner/non-Admin members who have accepted joining the org
|
||||||
if member.atype < UserOrgType::Admin && member.status != UserOrgStatus::Invited as i32 {
|
// Exclude invited and revoked users when checking for this policy.
|
||||||
let is_member_of_another_org = UserOrganization::find_any_state_by_user(&member.user_uuid, &conn)
|
// Those users will not be allowed to accept or be activated because of the policy checks done there.
|
||||||
.await
|
// We check if the count is larger then 1, because it includes this organization also.
|
||||||
.into_iter()
|
if member.atype < UserOrgType::Admin
|
||||||
// Other UserOrganization's where they have accepted being a member of
|
&& member.status != UserOrgStatus::Invited as i32
|
||||||
.filter(|uo| uo.uuid != member.uuid && uo.status != UserOrgStatus::Invited as i32)
|
&& UserOrganization::count_accepted_and_confirmed_by_user(&member.user_uuid, &conn).await > 1
|
||||||
.count()
|
{
|
||||||
> 1;
|
if CONFIG.mail_enabled() {
|
||||||
|
let org = Organization::find_by_uuid(&member.org_uuid, &conn).await.unwrap();
|
||||||
|
let user = User::find_by_uuid(&member.user_uuid, &conn).await.unwrap();
|
||||||
|
|
||||||
if is_member_of_another_org {
|
mail::send_single_org_removed_from_org(&user.email, &org.name).await?;
|
||||||
if CONFIG.mail_enabled() {
|
|
||||||
let org = Organization::find_by_uuid(&member.org_uuid, &conn).await.unwrap();
|
|
||||||
let user = User::find_by_uuid(&member.user_uuid, &conn).await.unwrap();
|
|
||||||
|
|
||||||
mail::send_single_org_removed_from_org(&user.email, &org.name).await?;
|
|
||||||
}
|
|
||||||
member.delete(&conn).await?;
|
|
||||||
}
|
}
|
||||||
|
member.delete(&conn).await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut policy = match OrgPolicy::find_by_org_and_type(&org_id, pol_type, &conn).await {
|
let mut policy = match OrgPolicy::find_by_org_and_type(&org_id, pol_type_enum, &conn).await {
|
||||||
Some(p) => p,
|
Some(p) => p,
|
||||||
None => OrgPolicy::new(org_id, pol_type_enum, "{}".to_string()),
|
None => OrgPolicy::new(org_id, pol_type_enum, "{}".to_string()),
|
||||||
};
|
};
|
||||||
@@ -1356,7 +1369,7 @@ fn get_organization_tax(org_id: String, _headers: Headers) -> Json<Value> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[get("/plans")]
|
#[get("/plans")]
|
||||||
fn get_plans(_headers: Headers) -> Json<Value> {
|
fn get_plans() -> Json<Value> {
|
||||||
// Respond with a minimal json just enough to allow the creation of an new organization.
|
// Respond with a minimal json just enough to allow the creation of an new organization.
|
||||||
Json(json!({
|
Json(json!({
|
||||||
"Object": "list",
|
"Object": "list",
|
||||||
@@ -1473,7 +1486,7 @@ async fn import(org_id: String, data: JsonUpcase<OrgImportData>, headers: Header
|
|||||||
|
|
||||||
// If this flag is enabled, any user that isn't provided in the Users list will be removed (by default they will be kept unless they have Deleted == true)
|
// If this flag is enabled, any user that isn't provided in the Users list will be removed (by default they will be kept unless they have Deleted == true)
|
||||||
if data.OverwriteExisting {
|
if data.OverwriteExisting {
|
||||||
for user_org in UserOrganization::find_by_org_and_type(&org_id, UserOrgType::User as i32, &conn).await {
|
for user_org in UserOrganization::find_by_org_and_type(&org_id, UserOrgType::User, &conn).await {
|
||||||
if let Some(user_email) = User::find_by_uuid(&user_org.user_uuid, &conn).await.map(|u| u.email) {
|
if let Some(user_email) = User::find_by_uuid(&user_org.user_uuid, &conn).await.map(|u| u.email) {
|
||||||
if !data.Users.iter().any(|u| u.Email == user_email) {
|
if !data.Users.iter().any(|u| u.Email == user_email) {
|
||||||
user_org.delete(&conn).await?;
|
user_org.delete(&conn).await?;
|
||||||
@@ -1484,3 +1497,226 @@ async fn import(org_id: String, data: JsonUpcase<OrgImportData>, headers: Header
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pre web-vault v2022.9.x endpoint
|
||||||
|
#[put("/organizations/<org_id>/users/<org_user_id>/deactivate")]
|
||||||
|
async fn deactivate_organization_user(
|
||||||
|
org_id: String,
|
||||||
|
org_user_id: String,
|
||||||
|
headers: AdminHeaders,
|
||||||
|
conn: DbConn,
|
||||||
|
) -> EmptyResult {
|
||||||
|
_revoke_organization_user(&org_id, &org_user_id, &headers, &conn).await
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pre web-vault v2022.9.x endpoint
|
||||||
|
#[put("/organizations/<org_id>/users/deactivate", data = "<data>")]
|
||||||
|
async fn bulk_deactivate_organization_user(
|
||||||
|
org_id: String,
|
||||||
|
data: JsonUpcase<Value>,
|
||||||
|
headers: AdminHeaders,
|
||||||
|
conn: DbConn,
|
||||||
|
) -> Json<Value> {
|
||||||
|
bulk_revoke_organization_user(org_id, data, headers, conn).await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[put("/organizations/<org_id>/users/<org_user_id>/revoke")]
|
||||||
|
async fn revoke_organization_user(
|
||||||
|
org_id: String,
|
||||||
|
org_user_id: String,
|
||||||
|
headers: AdminHeaders,
|
||||||
|
conn: DbConn,
|
||||||
|
) -> EmptyResult {
|
||||||
|
_revoke_organization_user(&org_id, &org_user_id, &headers, &conn).await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[put("/organizations/<org_id>/users/revoke", data = "<data>")]
|
||||||
|
async fn bulk_revoke_organization_user(
|
||||||
|
org_id: String,
|
||||||
|
data: JsonUpcase<Value>,
|
||||||
|
headers: AdminHeaders,
|
||||||
|
conn: DbConn,
|
||||||
|
) -> Json<Value> {
|
||||||
|
let data = data.into_inner().data;
|
||||||
|
|
||||||
|
let mut bulk_response = Vec::new();
|
||||||
|
match data["Ids"].as_array() {
|
||||||
|
Some(org_users) => {
|
||||||
|
for org_user_id in org_users {
|
||||||
|
let org_user_id = org_user_id.as_str().unwrap_or_default();
|
||||||
|
let err_msg = match _revoke_organization_user(&org_id, org_user_id, &headers, &conn).await {
|
||||||
|
Ok(_) => String::from(""),
|
||||||
|
Err(e) => format!("{:?}", e),
|
||||||
|
};
|
||||||
|
|
||||||
|
bulk_response.push(json!(
|
||||||
|
{
|
||||||
|
"Object": "OrganizationUserBulkResponseModel",
|
||||||
|
"Id": org_user_id,
|
||||||
|
"Error": err_msg
|
||||||
|
}
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => error!("No users to revoke"),
|
||||||
|
}
|
||||||
|
|
||||||
|
Json(json!({
|
||||||
|
"Data": bulk_response,
|
||||||
|
"Object": "list",
|
||||||
|
"ContinuationToken": null
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn _revoke_organization_user(
|
||||||
|
org_id: &str,
|
||||||
|
org_user_id: &str,
|
||||||
|
headers: &AdminHeaders,
|
||||||
|
conn: &DbConn,
|
||||||
|
) -> EmptyResult {
|
||||||
|
match UserOrganization::find_by_uuid_and_org(org_user_id, org_id, conn).await {
|
||||||
|
Some(mut user_org) if user_org.status > UserOrgStatus::Revoked as i32 => {
|
||||||
|
if user_org.user_uuid == headers.user.uuid {
|
||||||
|
err!("You cannot revoke yourself")
|
||||||
|
}
|
||||||
|
if user_org.atype == UserOrgType::Owner && headers.org_user_type != UserOrgType::Owner {
|
||||||
|
err!("Only owners can revoke other owners")
|
||||||
|
}
|
||||||
|
if user_org.atype == UserOrgType::Owner
|
||||||
|
&& UserOrganization::count_confirmed_by_org_and_type(org_id, UserOrgType::Owner, conn).await <= 1
|
||||||
|
{
|
||||||
|
err!("Organization must have at least one confirmed owner")
|
||||||
|
}
|
||||||
|
|
||||||
|
user_org.revoke();
|
||||||
|
user_org.save(conn).await?;
|
||||||
|
}
|
||||||
|
Some(_) => err!("User is already revoked"),
|
||||||
|
None => err!("User not found in organization"),
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pre web-vault v2022.9.x endpoint
|
||||||
|
#[put("/organizations/<org_id>/users/<org_user_id>/activate")]
|
||||||
|
async fn activate_organization_user(
|
||||||
|
org_id: String,
|
||||||
|
org_user_id: String,
|
||||||
|
headers: AdminHeaders,
|
||||||
|
conn: DbConn,
|
||||||
|
) -> EmptyResult {
|
||||||
|
_restore_organization_user(&org_id, &org_user_id, &headers, &conn).await
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pre web-vault v2022.9.x endpoint
|
||||||
|
#[put("/organizations/<org_id>/users/activate", data = "<data>")]
|
||||||
|
async fn bulk_activate_organization_user(
|
||||||
|
org_id: String,
|
||||||
|
data: JsonUpcase<Value>,
|
||||||
|
headers: AdminHeaders,
|
||||||
|
conn: DbConn,
|
||||||
|
) -> Json<Value> {
|
||||||
|
bulk_restore_organization_user(org_id, data, headers, conn).await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[put("/organizations/<org_id>/users/<org_user_id>/restore")]
|
||||||
|
async fn restore_organization_user(
|
||||||
|
org_id: String,
|
||||||
|
org_user_id: String,
|
||||||
|
headers: AdminHeaders,
|
||||||
|
conn: DbConn,
|
||||||
|
) -> EmptyResult {
|
||||||
|
_restore_organization_user(&org_id, &org_user_id, &headers, &conn).await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[put("/organizations/<org_id>/users/restore", data = "<data>")]
|
||||||
|
async fn bulk_restore_organization_user(
|
||||||
|
org_id: String,
|
||||||
|
data: JsonUpcase<Value>,
|
||||||
|
headers: AdminHeaders,
|
||||||
|
conn: DbConn,
|
||||||
|
) -> Json<Value> {
|
||||||
|
let data = data.into_inner().data;
|
||||||
|
|
||||||
|
let mut bulk_response = Vec::new();
|
||||||
|
match data["Ids"].as_array() {
|
||||||
|
Some(org_users) => {
|
||||||
|
for org_user_id in org_users {
|
||||||
|
let org_user_id = org_user_id.as_str().unwrap_or_default();
|
||||||
|
let err_msg = match _restore_organization_user(&org_id, org_user_id, &headers, &conn).await {
|
||||||
|
Ok(_) => String::from(""),
|
||||||
|
Err(e) => format!("{:?}", e),
|
||||||
|
};
|
||||||
|
|
||||||
|
bulk_response.push(json!(
|
||||||
|
{
|
||||||
|
"Object": "OrganizationUserBulkResponseModel",
|
||||||
|
"Id": org_user_id,
|
||||||
|
"Error": err_msg
|
||||||
|
}
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => error!("No users to restore"),
|
||||||
|
}
|
||||||
|
|
||||||
|
Json(json!({
|
||||||
|
"Data": bulk_response,
|
||||||
|
"Object": "list",
|
||||||
|
"ContinuationToken": null
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn _restore_organization_user(
|
||||||
|
org_id: &str,
|
||||||
|
org_user_id: &str,
|
||||||
|
headers: &AdminHeaders,
|
||||||
|
conn: &DbConn,
|
||||||
|
) -> EmptyResult {
|
||||||
|
match UserOrganization::find_by_uuid_and_org(org_user_id, org_id, conn).await {
|
||||||
|
Some(mut user_org) if user_org.status < UserOrgStatus::Accepted as i32 => {
|
||||||
|
if user_org.user_uuid == headers.user.uuid {
|
||||||
|
err!("You cannot restore yourself")
|
||||||
|
}
|
||||||
|
if user_org.atype == UserOrgType::Owner && headers.org_user_type != UserOrgType::Owner {
|
||||||
|
err!("Only owners can restore other owners")
|
||||||
|
}
|
||||||
|
|
||||||
|
// This check is also done at accept_invite(), _confirm_invite, _activate_user(), edit_user(), admin::update_user_org_type
|
||||||
|
// It returns different error messages per function.
|
||||||
|
if user_org.atype < UserOrgType::Admin {
|
||||||
|
match OrgPolicy::is_user_allowed(&user_org.user_uuid, org_id, false, conn).await {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(OrgPolicyErr::TwoFactorMissing) => {
|
||||||
|
err!("You cannot restore this user because it has no two-step login method activated");
|
||||||
|
}
|
||||||
|
Err(OrgPolicyErr::SingleOrgEnforced) => {
|
||||||
|
err!("You cannot restore this user because it is a member of an organization which forbids it");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
user_org.restore();
|
||||||
|
user_org.save(conn).await?;
|
||||||
|
}
|
||||||
|
Some(_) => err!("User is already active"),
|
||||||
|
None => err!("User not found in organization"),
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is a new function active since the v2022.9.x clients.
|
||||||
|
// It combines the previous two calls done before.
|
||||||
|
// We call those two functions here and combine them our selfs.
|
||||||
|
//
|
||||||
|
// NOTE: It seems clients can't handle uppercase-first keys!!
|
||||||
|
// We need to convert all keys so they have the first character to be a lowercase.
|
||||||
|
// Else the export will be just an empty JSON file.
|
||||||
|
#[get("/organizations/<org_id>/export")]
|
||||||
|
async fn get_org_export(org_id: String, headers: AdminHeaders, conn: DbConn) -> Json<Value> {
|
||||||
|
// Also both main keys here need to be lowercase, else the export will fail.
|
||||||
|
Json(json!({
|
||||||
|
"collections": convert_json_key_lcase_first(_get_org_collections(&org_id, &conn).await),
|
||||||
|
"ciphers": convert_json_key_lcase_first(_get_org_details(&org_id, &headers.host, &headers.user.uuid, &conn).await),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
@@ -17,6 +17,9 @@ use crate::{
|
|||||||
|
|
||||||
const SEND_INACCESSIBLE_MSG: &str = "Send does not exist or is no longer available";
|
const SEND_INACCESSIBLE_MSG: &str = "Send does not exist or is no longer available";
|
||||||
|
|
||||||
|
// The max file size allowed by Bitwarden clients and add an extra 5% to avoid issues
|
||||||
|
const SIZE_525_MB: u64 = 550_502_400;
|
||||||
|
|
||||||
pub fn routes() -> Vec<rocket::Route> {
|
pub fn routes() -> Vec<rocket::Route> {
|
||||||
routes![
|
routes![
|
||||||
get_sends,
|
get_sends,
|
||||||
@@ -28,7 +31,9 @@ pub fn routes() -> Vec<rocket::Route> {
|
|||||||
put_send,
|
put_send,
|
||||||
delete_send,
|
delete_send,
|
||||||
put_remove_password,
|
put_remove_password,
|
||||||
download_send
|
download_send,
|
||||||
|
post_send_file_v2,
|
||||||
|
post_send_file_v2_data
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,6 +63,7 @@ struct SendData {
|
|||||||
Notes: Option<String>,
|
Notes: Option<String>,
|
||||||
Text: Option<Value>,
|
Text: Option<Value>,
|
||||||
File: Option<Value>,
|
File: Option<Value>,
|
||||||
|
FileLength: Option<NumberOrString>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Enforces the `Disable Send` policy. A non-owner/admin user belonging to
|
/// Enforces the `Disable Send` policy. A non-owner/admin user belonging to
|
||||||
@@ -70,8 +76,9 @@ struct SendData {
|
|||||||
/// controls this policy globally.
|
/// controls this policy globally.
|
||||||
async fn enforce_disable_send_policy(headers: &Headers, conn: &DbConn) -> EmptyResult {
|
async fn enforce_disable_send_policy(headers: &Headers, conn: &DbConn) -> EmptyResult {
|
||||||
let user_uuid = &headers.user.uuid;
|
let user_uuid = &headers.user.uuid;
|
||||||
let policy_type = OrgPolicyType::DisableSend;
|
if !CONFIG.sends_allowed()
|
||||||
if !CONFIG.sends_allowed() || OrgPolicy::is_applicable_to_user(user_uuid, policy_type, conn).await {
|
|| OrgPolicy::is_applicable_to_user(user_uuid, OrgPolicyType::DisableSend, None, conn).await
|
||||||
|
{
|
||||||
err!("Due to an Enterprise Policy, you are only able to delete an existing Send.")
|
err!("Due to an Enterprise Policy, you are only able to delete an existing Send.")
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -184,6 +191,14 @@ struct UploadData<'f> {
|
|||||||
data: TempFile<'f>,
|
data: TempFile<'f>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(FromForm)]
|
||||||
|
struct UploadDataV2<'f> {
|
||||||
|
data: TempFile<'f>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// @deprecated Mar 25 2021: This method has been deprecated in favor of direct uploads (v2).
|
||||||
|
// This method still exists to support older clients, probably need to remove it sometime.
|
||||||
|
// Upstream: https://github.com/bitwarden/server/blob/d0c793c95181dfb1b447eb450f85ba0bfd7ef643/src/Api/Controllers/SendsController.cs#L164-L167
|
||||||
#[post("/sends/file", format = "multipart/form-data", data = "<data>")]
|
#[post("/sends/file", format = "multipart/form-data", data = "<data>")]
|
||||||
async fn post_send_file(data: Form<UploadData<'_>>, headers: Headers, conn: DbConn, nt: Notify<'_>) -> JsonResult {
|
async fn post_send_file(data: Form<UploadData<'_>>, headers: Headers, conn: DbConn, nt: Notify<'_>) -> JsonResult {
|
||||||
enforce_disable_send_policy(&headers, &conn).await?;
|
enforce_disable_send_policy(&headers, &conn).await?;
|
||||||
@@ -196,9 +211,6 @@ async fn post_send_file(data: Form<UploadData<'_>>, headers: Headers, conn: DbCo
|
|||||||
|
|
||||||
enforce_disable_hide_email_policy(&model, &headers, &conn).await?;
|
enforce_disable_hide_email_policy(&model, &headers, &conn).await?;
|
||||||
|
|
||||||
// Get the file length and add an extra 5% to avoid issues
|
|
||||||
const SIZE_525_MB: u64 = 550_502_400;
|
|
||||||
|
|
||||||
let size_limit = match CONFIG.user_attachment_limit() {
|
let size_limit = match CONFIG.user_attachment_limit() {
|
||||||
Some(0) => err!("File uploads are disabled"),
|
Some(0) => err!("File uploads are disabled"),
|
||||||
Some(limit_kb) => {
|
Some(limit_kb) => {
|
||||||
@@ -216,10 +228,12 @@ async fn post_send_file(data: Form<UploadData<'_>>, headers: Headers, conn: DbCo
|
|||||||
err!("Send content is not a file");
|
err!("Send content is not a file");
|
||||||
}
|
}
|
||||||
|
|
||||||
// There seems to be a bug somewhere regarding uploading attachments using the Android Client (Maybe iOS too?)
|
// There is a bug regarding uploading attachments/sends using the Mobile clients
|
||||||
// See: https://github.com/dani-garcia/vaultwarden/issues/2644
|
// See: https://github.com/dani-garcia/vaultwarden/issues/2644 && https://github.com/bitwarden/mobile/issues/2018
|
||||||
// Since all other clients seem to match TempFile::File and not TempFile::Buffered lets catch this and return an error for now.
|
// This has been fixed via a PR: https://github.com/bitwarden/mobile/pull/2031, but hasn't landed in a new release yet.
|
||||||
// We need to figure out how to solve this, but for now it's better to not accept these attachments since they will be broken.
|
// On the vaultwarden side this is temporarily fixed by using a custom multer library
|
||||||
|
// See: https://github.com/dani-garcia/vaultwarden/pull/2675
|
||||||
|
// In any case we will match TempFile::File and not TempFile::Buffered, since Buffered will alter the contents.
|
||||||
if let TempFile::Buffered {
|
if let TempFile::Buffered {
|
||||||
content: _,
|
content: _,
|
||||||
} = &data
|
} = &data
|
||||||
@@ -251,11 +265,110 @@ async fn post_send_file(data: Form<UploadData<'_>>, headers: Headers, conn: DbCo
|
|||||||
|
|
||||||
// Save the changes in the database
|
// Save the changes in the database
|
||||||
send.save(&conn).await?;
|
send.save(&conn).await?;
|
||||||
nt.send_send_update(UpdateType::SyncSendUpdate, &send, &send.update_users_revision(&conn).await).await;
|
nt.send_send_update(UpdateType::SyncSendCreate, &send, &send.update_users_revision(&conn).await).await;
|
||||||
|
|
||||||
Ok(Json(send.to_json()))
|
Ok(Json(send.to_json()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Upstream: https://github.com/bitwarden/server/blob/d0c793c95181dfb1b447eb450f85ba0bfd7ef643/src/Api/Controllers/SendsController.cs#L190
|
||||||
|
#[post("/sends/file/v2", data = "<data>")]
|
||||||
|
async fn post_send_file_v2(data: JsonUpcase<SendData>, headers: Headers, conn: DbConn) -> JsonResult {
|
||||||
|
enforce_disable_send_policy(&headers, &conn).await?;
|
||||||
|
|
||||||
|
let data = data.into_inner().data;
|
||||||
|
|
||||||
|
if data.Type != SendType::File as i32 {
|
||||||
|
err!("Send content is not a file");
|
||||||
|
}
|
||||||
|
|
||||||
|
enforce_disable_hide_email_policy(&data, &headers, &conn).await?;
|
||||||
|
|
||||||
|
let file_length = match &data.FileLength {
|
||||||
|
Some(m) => Some(m.into_i32()?),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let size_limit = match CONFIG.user_attachment_limit() {
|
||||||
|
Some(0) => err!("File uploads are disabled"),
|
||||||
|
Some(limit_kb) => {
|
||||||
|
let left = (limit_kb * 1024) - Attachment::size_by_user(&headers.user.uuid, &conn).await;
|
||||||
|
if left <= 0 {
|
||||||
|
err!("Attachment storage limit reached! Delete some attachments to free up space")
|
||||||
|
}
|
||||||
|
std::cmp::Ord::max(left as u64, SIZE_525_MB)
|
||||||
|
}
|
||||||
|
None => SIZE_525_MB,
|
||||||
|
};
|
||||||
|
|
||||||
|
if file_length.is_some() && file_length.unwrap() as u64 > size_limit {
|
||||||
|
err!("Attachment storage limit exceeded with this file");
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut send = create_send(data, headers.user.uuid)?;
|
||||||
|
|
||||||
|
let file_id = crate::crypto::generate_send_id();
|
||||||
|
|
||||||
|
let mut data_value: Value = serde_json::from_str(&send.data)?;
|
||||||
|
if let Some(o) = data_value.as_object_mut() {
|
||||||
|
o.insert(String::from("Id"), Value::String(file_id.clone()));
|
||||||
|
o.insert(String::from("Size"), Value::Number(file_length.unwrap().into()));
|
||||||
|
o.insert(String::from("SizeName"), Value::String(crate::util::get_display_size(file_length.unwrap())));
|
||||||
|
}
|
||||||
|
send.data = serde_json::to_string(&data_value)?;
|
||||||
|
send.save(&conn).await?;
|
||||||
|
|
||||||
|
Ok(Json(json!({
|
||||||
|
"fileUploadType": 0, // 0 == Direct | 1 == Azure
|
||||||
|
"object": "send-fileUpload",
|
||||||
|
"url": format!("/sends/{}/file/{}", send.uuid, file_id),
|
||||||
|
"sendResponse": send.to_json()
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://github.com/bitwarden/server/blob/d0c793c95181dfb1b447eb450f85ba0bfd7ef643/src/Api/Controllers/SendsController.cs#L243
|
||||||
|
#[post("/sends/<send_uuid>/file/<file_id>", format = "multipart/form-data", data = "<data>")]
|
||||||
|
async fn post_send_file_v2_data(
|
||||||
|
send_uuid: String,
|
||||||
|
file_id: String,
|
||||||
|
data: Form<UploadDataV2<'_>>,
|
||||||
|
headers: Headers,
|
||||||
|
conn: DbConn,
|
||||||
|
nt: Notify<'_>,
|
||||||
|
) -> EmptyResult {
|
||||||
|
enforce_disable_send_policy(&headers, &conn).await?;
|
||||||
|
|
||||||
|
let mut data = data.into_inner();
|
||||||
|
|
||||||
|
// There is a bug regarding uploading attachments/sends using the Mobile clients
|
||||||
|
// See: https://github.com/dani-garcia/vaultwarden/issues/2644 && https://github.com/bitwarden/mobile/issues/2018
|
||||||
|
// This has been fixed via a PR: https://github.com/bitwarden/mobile/pull/2031, but hasn't landed in a new release yet.
|
||||||
|
// On the vaultwarden side this is temporarily fixed by using a custom multer library
|
||||||
|
// See: https://github.com/dani-garcia/vaultwarden/pull/2675
|
||||||
|
// In any case we will match TempFile::File and not TempFile::Buffered, since Buffered will alter the contents.
|
||||||
|
if let TempFile::Buffered {
|
||||||
|
content: _,
|
||||||
|
} = &data.data
|
||||||
|
{
|
||||||
|
err!("Error reading attachment data. Please try an other client.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(send) = Send::find_by_uuid(&send_uuid, &conn).await {
|
||||||
|
let folder_path = tokio::fs::canonicalize(&CONFIG.sends_folder()).await?.join(&send_uuid);
|
||||||
|
let file_path = folder_path.join(&file_id);
|
||||||
|
tokio::fs::create_dir_all(&folder_path).await?;
|
||||||
|
|
||||||
|
if let Err(_err) = data.data.persist_to(&file_path).await {
|
||||||
|
data.data.move_copy_to(file_path).await?
|
||||||
|
}
|
||||||
|
|
||||||
|
nt.send_send_update(UpdateType::SyncSendCreate, &send, &send.update_users_revision(&conn).await).await;
|
||||||
|
} else {
|
||||||
|
err!("Send not found. Unable to save the file.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
pub struct SendAccessData {
|
pub struct SendAccessData {
|
||||||
|
@@ -19,7 +19,14 @@ pub mod webauthn;
|
|||||||
pub mod yubikey;
|
pub mod yubikey;
|
||||||
|
|
||||||
pub fn routes() -> Vec<Route> {
|
pub fn routes() -> Vec<Route> {
|
||||||
let mut routes = routes![get_twofactor, get_recover, recover, disable_twofactor, disable_twofactor_put,];
|
let mut routes = routes![
|
||||||
|
get_twofactor,
|
||||||
|
get_recover,
|
||||||
|
recover,
|
||||||
|
disable_twofactor,
|
||||||
|
disable_twofactor_put,
|
||||||
|
get_device_verification_settings,
|
||||||
|
];
|
||||||
|
|
||||||
routes.append(&mut authenticator::routes());
|
routes.append(&mut authenticator::routes());
|
||||||
routes.append(&mut duo::routes());
|
routes.append(&mut duo::routes());
|
||||||
@@ -188,3 +195,21 @@ pub async fn send_incomplete_2fa_notifications(pool: DbPool) {
|
|||||||
login.delete(&conn).await.expect("Error deleting incomplete 2FA record");
|
login.delete(&conn).await.expect("Error deleting incomplete 2FA record");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This function currently is just a dummy and the actual part is not implemented yet.
|
||||||
|
// This also prevents 404 errors.
|
||||||
|
//
|
||||||
|
// See the following Bitwarden PR's regarding this feature.
|
||||||
|
// https://github.com/bitwarden/clients/pull/2843
|
||||||
|
// https://github.com/bitwarden/clients/pull/2839
|
||||||
|
// https://github.com/bitwarden/server/pull/2016
|
||||||
|
//
|
||||||
|
// The HTML part is hidden via the CSS patches done via the bw_web_build repo
|
||||||
|
#[get("/two-factor/get-device-verification-settings")]
|
||||||
|
fn get_device_verification_settings(_headers: Headers, _conn: DbConn) -> Json<Value> {
|
||||||
|
Json(json!({
|
||||||
|
"isDeviceVerificationSectionEnabled":false,
|
||||||
|
"unknownDeviceVerificationEnabled":false,
|
||||||
|
"object":"deviceVerificationSettings"
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
@@ -10,6 +10,7 @@ use serde_json::Value;
|
|||||||
|
|
||||||
pub use crate::api::{
|
pub use crate::api::{
|
||||||
admin::routes as admin_routes,
|
admin::routes as admin_routes,
|
||||||
|
core::catchers as core_catchers,
|
||||||
core::purge_sends,
|
core::purge_sends,
|
||||||
core::purge_trashed_ciphers,
|
core::purge_trashed_ciphers,
|
||||||
core::routes as core_routes,
|
core::routes as core_routes,
|
||||||
@@ -19,6 +20,7 @@ pub use crate::api::{
|
|||||||
identity::routes as identity_routes,
|
identity::routes as identity_routes,
|
||||||
notifications::routes as notifications_routes,
|
notifications::routes as notifications_routes,
|
||||||
notifications::{start_notification_server, Notify, UpdateType},
|
notifications::{start_notification_server, Notify, UpdateType},
|
||||||
|
web::catchers as web_catchers,
|
||||||
web::routes as web_routes,
|
web::routes as web_routes,
|
||||||
};
|
};
|
||||||
use crate::util;
|
use crate::util;
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use rocket::serde::json::Json;
|
use rocket::serde::json::Json;
|
||||||
use rocket::{fs::NamedFile, http::ContentType, Route};
|
use rocket::{fs::NamedFile, http::ContentType, Catcher, Route};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -21,6 +21,19 @@ pub fn routes() -> Vec<Route> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn catchers() -> Vec<Catcher> {
|
||||||
|
if CONFIG.web_vault_enabled() {
|
||||||
|
catchers![not_found]
|
||||||
|
} else {
|
||||||
|
catchers![]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[catch(404)]
|
||||||
|
async fn not_found() -> Cached<Option<NamedFile>> {
|
||||||
|
Cached::short(NamedFile::open(Path::new(&CONFIG.web_vault_folder()).join("404.html")).await.ok(), false)
|
||||||
|
}
|
||||||
|
|
||||||
#[get("/")]
|
#[get("/")]
|
||||||
async fn web_index() -> Cached<Option<NamedFile>> {
|
async fn web_index() -> Cached<Option<NamedFile>> {
|
||||||
Cached::short(NamedFile::open(Path::new(&CONFIG.web_vault_folder()).join("index.html")).await.ok(), false)
|
Cached::short(NamedFile::open(Path::new(&CONFIG.web_vault_folder()).join("index.html")).await.ok(), false)
|
||||||
@@ -88,8 +101,8 @@ fn static_files(filename: String) -> Result<(ContentType, &'static [u8]), Error>
|
|||||||
"identicon.js" => Ok((ContentType::JavaScript, include_bytes!("../static/scripts/identicon.js"))),
|
"identicon.js" => Ok((ContentType::JavaScript, include_bytes!("../static/scripts/identicon.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.6.0.slim.js" => {
|
"jquery-3.6.1.slim.js" => {
|
||||||
Ok((ContentType::JavaScript, include_bytes!("../static/scripts/jquery-3.6.0.slim.js")))
|
Ok((ContentType::JavaScript, include_bytes!("../static/scripts/jquery-3.6.1.slim.js")))
|
||||||
}
|
}
|
||||||
_ => err!(format!("Static file not found: {}", filename)),
|
_ => err!(format!("Static file not found: {}", filename)),
|
||||||
}
|
}
|
||||||
|
30
src/auth.rs
30
src/auth.rs
@@ -1,18 +1,14 @@
|
|||||||
//
|
|
||||||
// JWT Handling
|
// JWT Handling
|
||||||
//
|
//
|
||||||
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;
|
||||||
|
|
||||||
use jsonwebtoken::{self, Algorithm, DecodingKey, EncodingKey, Header};
|
use jsonwebtoken::{self, errors::ErrorKind, Algorithm, DecodingKey, EncodingKey, Header};
|
||||||
use serde::de::DeserializeOwned;
|
use serde::de::DeserializeOwned;
|
||||||
use serde::ser::Serialize;
|
use serde::ser::Serialize;
|
||||||
|
|
||||||
use crate::{
|
use crate::{error::Error, CONFIG};
|
||||||
error::{Error, MapResult},
|
|
||||||
CONFIG,
|
|
||||||
};
|
|
||||||
|
|
||||||
const JWT_ALGORITHM: Algorithm = Algorithm::RS256;
|
const JWT_ALGORITHM: Algorithm = Algorithm::RS256;
|
||||||
|
|
||||||
@@ -61,7 +57,15 @@ 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, "");
|
||||||
jsonwebtoken::decode(&token, &PUBLIC_RSA_KEY, &validation).map(|d| d.claims).map_res("Error decoding JWT")
|
match jsonwebtoken::decode(&token, &PUBLIC_RSA_KEY, &validation) {
|
||||||
|
Ok(d) => Ok(d.claims),
|
||||||
|
Err(err) => match *err.kind() {
|
||||||
|
ErrorKind::InvalidToken => err!("Token is invalid"),
|
||||||
|
ErrorKind::InvalidIssuer => err!("Issuer is invalid"),
|
||||||
|
ErrorKind::ExpiredSignature => err!("Token has expired"),
|
||||||
|
_ => err!("Error decoding JWT"),
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn decode_login(token: &str) -> Result<LoginJwtClaims, Error> {
|
pub fn decode_login(token: &str) -> Result<LoginJwtClaims, Error> {
|
||||||
@@ -148,9 +152,10 @@ pub fn generate_invite_claims(
|
|||||||
invited_by_email: Option<String>,
|
invited_by_email: Option<String>,
|
||||||
) -> InviteJwtClaims {
|
) -> InviteJwtClaims {
|
||||||
let time_now = Utc::now().naive_utc();
|
let time_now = Utc::now().naive_utc();
|
||||||
|
let expire_hours = i64::from(CONFIG.invitation_expiration_hours());
|
||||||
InviteJwtClaims {
|
InviteJwtClaims {
|
||||||
nbf: time_now.timestamp(),
|
nbf: time_now.timestamp(),
|
||||||
exp: (time_now + Duration::days(5)).timestamp(),
|
exp: (time_now + Duration::hours(expire_hours)).timestamp(),
|
||||||
iss: JWT_INVITE_ISSUER.to_string(),
|
iss: JWT_INVITE_ISSUER.to_string(),
|
||||||
sub: uuid,
|
sub: uuid,
|
||||||
email,
|
email,
|
||||||
@@ -185,9 +190,10 @@ pub fn generate_emergency_access_invite_claims(
|
|||||||
grantor_email: Option<String>,
|
grantor_email: Option<String>,
|
||||||
) -> EmergencyAccessInviteJwtClaims {
|
) -> EmergencyAccessInviteJwtClaims {
|
||||||
let time_now = Utc::now().naive_utc();
|
let time_now = Utc::now().naive_utc();
|
||||||
|
let expire_hours = i64::from(CONFIG.invitation_expiration_hours());
|
||||||
EmergencyAccessInviteJwtClaims {
|
EmergencyAccessInviteJwtClaims {
|
||||||
nbf: time_now.timestamp(),
|
nbf: time_now.timestamp(),
|
||||||
exp: (time_now + Duration::days(5)).timestamp(),
|
exp: (time_now + Duration::hours(expire_hours)).timestamp(),
|
||||||
iss: JWT_EMERGENCY_ACCESS_INVITE_ISSUER.to_string(),
|
iss: JWT_EMERGENCY_ACCESS_INVITE_ISSUER.to_string(),
|
||||||
sub: uuid,
|
sub: uuid,
|
||||||
email,
|
email,
|
||||||
@@ -211,9 +217,10 @@ pub struct BasicJwtClaims {
|
|||||||
|
|
||||||
pub fn generate_delete_claims(uuid: String) -> BasicJwtClaims {
|
pub fn generate_delete_claims(uuid: String) -> BasicJwtClaims {
|
||||||
let time_now = Utc::now().naive_utc();
|
let time_now = Utc::now().naive_utc();
|
||||||
|
let expire_hours = i64::from(CONFIG.invitation_expiration_hours());
|
||||||
BasicJwtClaims {
|
BasicJwtClaims {
|
||||||
nbf: time_now.timestamp(),
|
nbf: time_now.timestamp(),
|
||||||
exp: (time_now + Duration::days(5)).timestamp(),
|
exp: (time_now + Duration::hours(expire_hours)).timestamp(),
|
||||||
iss: JWT_DELETE_ISSUER.to_string(),
|
iss: JWT_DELETE_ISSUER.to_string(),
|
||||||
sub: uuid,
|
sub: uuid,
|
||||||
}
|
}
|
||||||
@@ -221,9 +228,10 @@ pub fn generate_delete_claims(uuid: String) -> BasicJwtClaims {
|
|||||||
|
|
||||||
pub fn generate_verify_email_claims(uuid: String) -> BasicJwtClaims {
|
pub fn generate_verify_email_claims(uuid: String) -> BasicJwtClaims {
|
||||||
let time_now = Utc::now().naive_utc();
|
let time_now = Utc::now().naive_utc();
|
||||||
|
let expire_hours = i64::from(CONFIG.invitation_expiration_hours());
|
||||||
BasicJwtClaims {
|
BasicJwtClaims {
|
||||||
nbf: time_now.timestamp(),
|
nbf: time_now.timestamp(),
|
||||||
exp: (time_now + Duration::days(5)).timestamp(),
|
exp: (time_now + Duration::hours(expire_hours)).timestamp(),
|
||||||
iss: JWT_VERIFYEMAIL_ISSUER.to_string(),
|
iss: JWT_VERIFYEMAIL_ISSUER.to_string(),
|
||||||
sub: uuid,
|
sub: uuid,
|
||||||
}
|
}
|
||||||
|
@@ -430,6 +430,9 @@ make_config! {
|
|||||||
org_creation_users: String, true, def, "".to_string();
|
org_creation_users: String, true, def, "".to_string();
|
||||||
/// Allow invitations |> Controls whether users can be invited by organization admins, even when signups are otherwise disabled
|
/// Allow invitations |> Controls whether users can be invited by organization admins, even when signups are otherwise disabled
|
||||||
invitations_allowed: bool, true, def, true;
|
invitations_allowed: bool, true, def, true;
|
||||||
|
/// Invitation token expiration time (in hours) |> The number of hours after which an organization invite token, emergency access invite token,
|
||||||
|
/// email verification token and deletion request token will expire (must be at least 1)
|
||||||
|
invitation_expiration_hours: u32, false, def, 120;
|
||||||
/// Allow emergency access |> Controls whether users can enable emergency access to their accounts. This setting applies globally to all users.
|
/// Allow emergency access |> Controls whether users can enable emergency access to their accounts. This setting applies globally to all users.
|
||||||
emergency_access_allowed: bool, true, def, true;
|
emergency_access_allowed: bool, true, def, true;
|
||||||
/// Password iterations |> Number of server-side passwords hashing iterations.
|
/// Password iterations |> Number of server-side passwords hashing iterations.
|
||||||
@@ -726,6 +729,10 @@ fn validate_config(cfg: &ConfigItems) -> Result<(), Error> {
|
|||||||
_ => err!("Only HTTP 301/302 and 307/308 redirects are supported"),
|
_ => err!("Only HTTP 301/302 and 307/308 redirects are supported"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if cfg.invitation_expiration_hours < 1 {
|
||||||
|
err!("`INVITATION_EXPIRATION_HOURS` has a minimum duration of 1 hour")
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -160,6 +160,7 @@ impl Cipher {
|
|||||||
"Object": "cipherDetails",
|
"Object": "cipherDetails",
|
||||||
"Id": self.uuid,
|
"Id": self.uuid,
|
||||||
"Type": self.atype,
|
"Type": self.atype,
|
||||||
|
"CreationDate": format_date(&self.created_at),
|
||||||
"RevisionDate": format_date(&self.updated_at),
|
"RevisionDate": format_date(&self.updated_at),
|
||||||
"DeletedDate": self.deleted_at.map_or(Value::Null, |d| Value::String(format_date(&d))),
|
"DeletedDate": self.deleted_at.map_or(Value::Null, |d| Value::String(format_date(&d))),
|
||||||
"FolderId": if let Some(cipher_sync_data) = cipher_sync_data { cipher_sync_data.cipher_folders.get(&self.uuid).map(|c| c.to_string() ) } else { self.get_folder_uuid(user_uuid, conn).await },
|
"FolderId": if let Some(cipher_sync_data) = cipher_sync_data { cipher_sync_data.cipher_folders.get(&self.uuid).map(|c| c.to_string() ) } else { self.get_folder_uuid(user_uuid, conn).await },
|
||||||
|
@@ -19,7 +19,7 @@ pub use self::device::Device;
|
|||||||
pub use self::emergency_access::{EmergencyAccess, EmergencyAccessStatus, EmergencyAccessType};
|
pub use self::emergency_access::{EmergencyAccess, EmergencyAccessStatus, EmergencyAccessType};
|
||||||
pub use self::favorite::Favorite;
|
pub use self::favorite::Favorite;
|
||||||
pub use self::folder::{Folder, FolderCipher};
|
pub use self::folder::{Folder, FolderCipher};
|
||||||
pub use self::org_policy::{OrgPolicy, OrgPolicyType};
|
pub use self::org_policy::{OrgPolicy, OrgPolicyErr, OrgPolicyType};
|
||||||
pub use self::organization::{Organization, UserOrgStatus, UserOrgType, UserOrganization};
|
pub use self::organization::{Organization, UserOrgStatus, UserOrgType, UserOrganization};
|
||||||
pub use self::send::{Send, SendType};
|
pub use self::send::{Send, SendType};
|
||||||
pub use self::two_factor::{TwoFactor, TwoFactorType};
|
pub use self::two_factor::{TwoFactor, TwoFactorType};
|
||||||
|
@@ -6,7 +6,7 @@ use crate::db::DbConn;
|
|||||||
use crate::error::MapResult;
|
use crate::error::MapResult;
|
||||||
use crate::util::UpCase;
|
use crate::util::UpCase;
|
||||||
|
|
||||||
use super::{UserOrgStatus, UserOrgType, UserOrganization};
|
use super::{TwoFactor, UserOrgStatus, UserOrgType, UserOrganization};
|
||||||
|
|
||||||
db_object! {
|
db_object! {
|
||||||
#[derive(Identifiable, Queryable, Insertable, AsChangeset)]
|
#[derive(Identifiable, Queryable, Insertable, AsChangeset)]
|
||||||
@@ -21,25 +21,37 @@ db_object! {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://github.com/bitwarden/server/blob/b86a04cef9f1e1b82cf18e49fc94e017c641130c/src/Core/Enums/PolicyType.cs
|
||||||
#[derive(Copy, Clone, Eq, PartialEq, num_derive::FromPrimitive)]
|
#[derive(Copy, Clone, Eq, PartialEq, num_derive::FromPrimitive)]
|
||||||
pub enum OrgPolicyType {
|
pub enum OrgPolicyType {
|
||||||
TwoFactorAuthentication = 0,
|
TwoFactorAuthentication = 0,
|
||||||
MasterPassword = 1,
|
MasterPassword = 1,
|
||||||
PasswordGenerator = 2,
|
PasswordGenerator = 2,
|
||||||
SingleOrg = 3,
|
SingleOrg = 3,
|
||||||
// RequireSso = 4, // Not currently supported.
|
// RequireSso = 4, // Not supported
|
||||||
PersonalOwnership = 5,
|
PersonalOwnership = 5,
|
||||||
DisableSend = 6,
|
DisableSend = 6,
|
||||||
SendOptions = 7,
|
SendOptions = 7,
|
||||||
|
// ResetPassword = 8, // Not supported
|
||||||
|
// MaximumVaultTimeout = 9, // Not supported (Not AGPLv3 Licensed)
|
||||||
|
// DisablePersonalVaultExport = 10, // Not supported (Not AGPLv3 Licensed)
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://github.com/bitwarden/server/blob/master/src/Core/Models/Data/SendOptionsPolicyData.cs
|
// https://github.com/bitwarden/server/blob/5cbdee137921a19b1f722920f0fa3cd45af2ef0f/src/Core/Models/Data/Organizations/Policies/SendOptionsPolicyData.cs
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
pub struct SendOptionsPolicyData {
|
pub struct SendOptionsPolicyData {
|
||||||
pub DisableHideEmail: bool,
|
pub DisableHideEmail: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub type OrgPolicyResult = Result<(), OrgPolicyErr>;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum OrgPolicyErr {
|
||||||
|
TwoFactorMissing,
|
||||||
|
SingleOrgEnforced,
|
||||||
|
}
|
||||||
|
|
||||||
/// Local methods
|
/// Local methods
|
||||||
impl OrgPolicy {
|
impl OrgPolicy {
|
||||||
pub fn new(org_uuid: String, atype: OrgPolicyType, data: String) -> Self {
|
pub fn new(org_uuid: String, atype: OrgPolicyType, data: String) -> Self {
|
||||||
@@ -160,11 +172,11 @@ impl OrgPolicy {
|
|||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn find_by_org_and_type(org_uuid: &str, atype: i32, conn: &DbConn) -> Option<Self> {
|
pub async fn find_by_org_and_type(org_uuid: &str, policy_type: OrgPolicyType, conn: &DbConn) -> Option<Self> {
|
||||||
db_run! { conn: {
|
db_run! { conn: {
|
||||||
org_policies::table
|
org_policies::table
|
||||||
.filter(org_policies::org_uuid.eq(org_uuid))
|
.filter(org_policies::org_uuid.eq(org_uuid))
|
||||||
.filter(org_policies::atype.eq(atype))
|
.filter(org_policies::atype.eq(policy_type as i32))
|
||||||
.first::<OrgPolicyDb>(conn)
|
.first::<OrgPolicyDb>(conn)
|
||||||
.ok()
|
.ok()
|
||||||
.from_db()
|
.from_db()
|
||||||
@@ -179,40 +191,128 @@ impl OrgPolicy {
|
|||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn find_accepted_and_confirmed_by_user_and_active_policy(
|
||||||
|
user_uuid: &str,
|
||||||
|
policy_type: OrgPolicyType,
|
||||||
|
conn: &DbConn,
|
||||||
|
) -> Vec<Self> {
|
||||||
|
db_run! { conn: {
|
||||||
|
org_policies::table
|
||||||
|
.inner_join(
|
||||||
|
users_organizations::table.on(
|
||||||
|
users_organizations::org_uuid.eq(org_policies::org_uuid)
|
||||||
|
.and(users_organizations::user_uuid.eq(user_uuid)))
|
||||||
|
)
|
||||||
|
.filter(
|
||||||
|
users_organizations::status.eq(UserOrgStatus::Accepted as i32)
|
||||||
|
)
|
||||||
|
.or_filter(
|
||||||
|
users_organizations::status.eq(UserOrgStatus::Confirmed as i32)
|
||||||
|
)
|
||||||
|
.filter(org_policies::atype.eq(policy_type as i32))
|
||||||
|
.filter(org_policies::enabled.eq(true))
|
||||||
|
.select(org_policies::all_columns)
|
||||||
|
.load::<OrgPolicyDb>(conn)
|
||||||
|
.expect("Error loading org_policy")
|
||||||
|
.from_db()
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn find_confirmed_by_user_and_active_policy(
|
||||||
|
user_uuid: &str,
|
||||||
|
policy_type: OrgPolicyType,
|
||||||
|
conn: &DbConn,
|
||||||
|
) -> Vec<Self> {
|
||||||
|
db_run! { conn: {
|
||||||
|
org_policies::table
|
||||||
|
.inner_join(
|
||||||
|
users_organizations::table.on(
|
||||||
|
users_organizations::org_uuid.eq(org_policies::org_uuid)
|
||||||
|
.and(users_organizations::user_uuid.eq(user_uuid)))
|
||||||
|
)
|
||||||
|
.filter(
|
||||||
|
users_organizations::status.eq(UserOrgStatus::Confirmed as i32)
|
||||||
|
)
|
||||||
|
.filter(org_policies::atype.eq(policy_type as i32))
|
||||||
|
.filter(org_policies::enabled.eq(true))
|
||||||
|
.select(org_policies::all_columns)
|
||||||
|
.load::<OrgPolicyDb>(conn)
|
||||||
|
.expect("Error loading org_policy")
|
||||||
|
.from_db()
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns true if the user belongs to an org that has enabled the specified policy type,
|
/// Returns true if the user belongs to an org that has enabled the specified policy type,
|
||||||
/// and the user is not an owner or admin of that org. This is only useful for checking
|
/// and the user is not an owner or admin of that org. This is only useful for checking
|
||||||
/// applicability of policy types that have these particular semantics.
|
/// applicability of policy types that have these particular semantics.
|
||||||
pub async fn is_applicable_to_user(user_uuid: &str, policy_type: OrgPolicyType, conn: &DbConn) -> bool {
|
pub async fn is_applicable_to_user(
|
||||||
// TODO: Should check confirmed and accepted users
|
user_uuid: &str,
|
||||||
for policy in OrgPolicy::find_confirmed_by_user(user_uuid, conn).await {
|
policy_type: OrgPolicyType,
|
||||||
if policy.enabled && policy.has_type(policy_type) {
|
exclude_org_uuid: Option<&str>,
|
||||||
let org_uuid = &policy.org_uuid;
|
conn: &DbConn,
|
||||||
if let Some(user) = UserOrganization::find_by_user_and_org(user_uuid, org_uuid, conn).await {
|
) -> bool {
|
||||||
if user.atype < UserOrgType::Admin {
|
for policy in
|
||||||
return true;
|
OrgPolicy::find_accepted_and_confirmed_by_user_and_active_policy(user_uuid, policy_type, conn).await
|
||||||
}
|
{
|
||||||
|
// Check if we need to skip this organization.
|
||||||
|
if exclude_org_uuid.is_some() && exclude_org_uuid.unwrap() == policy.org_uuid {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(user) = UserOrganization::find_by_user_and_org(user_uuid, &policy.org_uuid, conn).await {
|
||||||
|
if user.atype < UserOrgType::Admin {
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn is_user_allowed(
|
||||||
|
user_uuid: &str,
|
||||||
|
org_uuid: &str,
|
||||||
|
exclude_current_org: bool,
|
||||||
|
conn: &DbConn,
|
||||||
|
) -> OrgPolicyResult {
|
||||||
|
// Enforce TwoFactor/TwoStep login
|
||||||
|
if TwoFactor::find_by_user(user_uuid, conn).await.is_empty() {
|
||||||
|
match Self::find_by_org_and_type(org_uuid, OrgPolicyType::TwoFactorAuthentication, conn).await {
|
||||||
|
Some(p) if p.enabled => {
|
||||||
|
return Err(OrgPolicyErr::TwoFactorMissing);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enforce Single Organization Policy of other organizations user is a member of
|
||||||
|
// This check here needs to exclude this current org-id, else an accepted user can not be confirmed.
|
||||||
|
let exclude_org = if exclude_current_org {
|
||||||
|
Some(org_uuid)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
if Self::is_applicable_to_user(user_uuid, OrgPolicyType::SingleOrg, exclude_org, conn).await {
|
||||||
|
return Err(OrgPolicyErr::SingleOrgEnforced);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns true if the user belongs to an org that has enabled the `DisableHideEmail`
|
/// Returns true if the user belongs to an org that has enabled the `DisableHideEmail`
|
||||||
/// option of the `Send Options` policy, and the user is not an owner or admin of that org.
|
/// option of the `Send Options` policy, and the user is not an owner or admin of that org.
|
||||||
pub async fn is_hide_email_disabled(user_uuid: &str, conn: &DbConn) -> bool {
|
pub async fn is_hide_email_disabled(user_uuid: &str, conn: &DbConn) -> bool {
|
||||||
for policy in OrgPolicy::find_confirmed_by_user(user_uuid, conn).await {
|
for policy in
|
||||||
if policy.enabled && policy.has_type(OrgPolicyType::SendOptions) {
|
OrgPolicy::find_confirmed_by_user_and_active_policy(user_uuid, OrgPolicyType::SendOptions, conn).await
|
||||||
let org_uuid = &policy.org_uuid;
|
{
|
||||||
if let Some(user) = UserOrganization::find_by_user_and_org(user_uuid, org_uuid, conn).await {
|
if let Some(user) = UserOrganization::find_by_user_and_org(user_uuid, &policy.org_uuid, conn).await {
|
||||||
if user.atype < UserOrgType::Admin {
|
if user.atype < UserOrgType::Admin {
|
||||||
match serde_json::from_str::<UpCase<SendOptionsPolicyData>>(&policy.data) {
|
match serde_json::from_str::<UpCase<SendOptionsPolicyData>>(&policy.data) {
|
||||||
Ok(opts) => {
|
Ok(opts) => {
|
||||||
if opts.data.DisableHideEmail {
|
if opts.data.DisableHideEmail {
|
||||||
return true;
|
return true;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
_ => error!("Failed to deserialize policy data: {}", policy.data),
|
|
||||||
}
|
}
|
||||||
|
_ => error!("Failed to deserialize SendOptionsPolicyData: {}", policy.data),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -31,7 +31,9 @@ db_object! {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://github.com/bitwarden/server/blob/b86a04cef9f1e1b82cf18e49fc94e017c641130c/src/Core/Enums/OrganizationUserStatusType.cs
|
||||||
pub enum UserOrgStatus {
|
pub enum UserOrgStatus {
|
||||||
|
Revoked = -1,
|
||||||
Invited = 0,
|
Invited = 0,
|
||||||
Accepted = 1,
|
Accepted = 1,
|
||||||
Confirmed = 2,
|
Confirmed = 2,
|
||||||
@@ -133,26 +135,29 @@ impl Organization {
|
|||||||
public_key,
|
public_key,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// https://github.com/bitwarden/server/blob/13d1e74d6960cf0d042620b72d85bf583a4236f7/src/Api/Models/Response/Organizations/OrganizationResponseModel.cs
|
||||||
pub fn to_json(&self) -> Value {
|
pub fn to_json(&self) -> Value {
|
||||||
json!({
|
json!({
|
||||||
"Id": self.uuid,
|
"Id": self.uuid,
|
||||||
"Identifier": null, // not supported by us
|
"Identifier": null, // not supported by us
|
||||||
"Name": self.name,
|
"Name": self.name,
|
||||||
"Seats": 10, // The value doesn't matter, we don't check server-side
|
"Seats": 10, // The value doesn't matter, we don't check server-side
|
||||||
|
// "MaxAutoscaleSeats": null, // The value doesn't matter, we don't check server-side
|
||||||
"MaxCollections": 10, // The value doesn't matter, we don't check server-side
|
"MaxCollections": 10, // The value doesn't matter, we don't check server-side
|
||||||
"MaxStorageGb": 10, // The value doesn't matter, we don't check server-side
|
"MaxStorageGb": 10, // The value doesn't matter, we don't check server-side
|
||||||
"Use2fa": true,
|
"Use2fa": true,
|
||||||
"UseDirectory": false, // Is supported, but this value isn't checked anywhere (yet)
|
"UseDirectory": false, // Is supported, but this value isn't checked anywhere (yet)
|
||||||
"UseEvents": false, // not supported by us
|
"UseEvents": false, // Not supported
|
||||||
"UseGroups": false, // not supported by us
|
"UseGroups": false, // Not supported
|
||||||
"UseTotp": true,
|
"UseTotp": true,
|
||||||
"UsePolicies": true,
|
"UsePolicies": true,
|
||||||
"UseSso": false, // We do not support SSO
|
// "UseScim": false, // Not supported (Not AGPLv3 Licensed)
|
||||||
|
"UseSso": false, // Not supported
|
||||||
|
// "UseKeyConnector": false, // Not supported
|
||||||
"SelfHost": true,
|
"SelfHost": true,
|
||||||
"UseApi": false, // not supported by us
|
"UseApi": false, // Not supported
|
||||||
"HasPublicAndPrivateKeys": self.private_key.is_some() && self.public_key.is_some(),
|
"HasPublicAndPrivateKeys": self.private_key.is_some() && self.public_key.is_some(),
|
||||||
"ResetPasswordEnrolled": false, // not supported by us
|
"UseResetPassword": false, // Not supported
|
||||||
|
|
||||||
"BusinessName": null,
|
"BusinessName": null,
|
||||||
"BusinessAddress1": null,
|
"BusinessAddress1": null,
|
||||||
@@ -170,6 +175,12 @@ impl Organization {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Used to either subtract or add to the current status
|
||||||
|
// The number 128 should be fine, it is well within the range of an i32
|
||||||
|
// The same goes for the database where we only use INTEGER (the same as an i32)
|
||||||
|
// It should also provide enough room for 100+ types, which i doubt will ever happen.
|
||||||
|
static ACTIVATE_REVOKE_DIFF: i32 = 128;
|
||||||
|
|
||||||
impl UserOrganization {
|
impl UserOrganization {
|
||||||
pub fn new(user_uuid: String, org_uuid: String) -> Self {
|
pub fn new(user_uuid: String, org_uuid: String) -> Self {
|
||||||
Self {
|
Self {
|
||||||
@@ -184,6 +195,18 @@ impl UserOrganization {
|
|||||||
atype: UserOrgType::User as i32,
|
atype: UserOrgType::User as i32,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn restore(&mut self) {
|
||||||
|
if self.status < UserOrgStatus::Accepted as i32 {
|
||||||
|
self.status += ACTIVATE_REVOKE_DIFF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn revoke(&mut self) {
|
||||||
|
if self.status > UserOrgStatus::Revoked as i32 {
|
||||||
|
self.status -= ACTIVATE_REVOKE_DIFF;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
use crate::db::DbConn;
|
use crate::db::DbConn;
|
||||||
@@ -265,9 +288,10 @@ impl UserOrganization {
|
|||||||
pub async fn to_json(&self, conn: &DbConn) -> Value {
|
pub async fn to_json(&self, conn: &DbConn) -> Value {
|
||||||
let org = Organization::find_by_uuid(&self.org_uuid, conn).await.unwrap();
|
let org = Organization::find_by_uuid(&self.org_uuid, conn).await.unwrap();
|
||||||
|
|
||||||
|
// https://github.com/bitwarden/server/blob/13d1e74d6960cf0d042620b72d85bf583a4236f7/src/Api/Models/Response/ProfileOrganizationResponseModel.cs
|
||||||
json!({
|
json!({
|
||||||
"Id": self.org_uuid,
|
"Id": self.org_uuid,
|
||||||
"Identifier": null, // not supported by us
|
"Identifier": null, // Not supported
|
||||||
"Name": org.name,
|
"Name": org.name,
|
||||||
"Seats": 10, // The value doesn't matter, we don't check server-side
|
"Seats": 10, // The value doesn't matter, we don't check server-side
|
||||||
"MaxCollections": 10, // The value doesn't matter, we don't check server-side
|
"MaxCollections": 10, // The value doesn't matter, we don't check server-side
|
||||||
@@ -275,44 +299,48 @@ impl UserOrganization {
|
|||||||
|
|
||||||
"Use2fa": true,
|
"Use2fa": true,
|
||||||
"UseDirectory": false, // Is supported, but this value isn't checked anywhere (yet)
|
"UseDirectory": false, // Is supported, but this value isn't checked anywhere (yet)
|
||||||
"UseEvents": false, // not supported by us
|
"UseEvents": false, // Not supported
|
||||||
"UseGroups": false, // not supported by us
|
"UseGroups": false, // Not supported
|
||||||
"UseTotp": true,
|
"UseTotp": true,
|
||||||
|
// "UseScim": false, // Not supported (Not AGPLv3 Licensed)
|
||||||
"UsePolicies": true,
|
"UsePolicies": true,
|
||||||
"UseApi": false, // not supported by us
|
"UseApi": false, // Not supported
|
||||||
"SelfHost": true,
|
"SelfHost": true,
|
||||||
"HasPublicAndPrivateKeys": org.private_key.is_some() && org.public_key.is_some(),
|
"HasPublicAndPrivateKeys": org.private_key.is_some() && org.public_key.is_some(),
|
||||||
"ResetPasswordEnrolled": false, // not supported by us
|
"ResetPasswordEnrolled": false, // Not supported
|
||||||
"SsoBound": false, // We do not support SSO
|
"SsoBound": false, // Not supported
|
||||||
"UseSso": false, // We do not support SSO
|
"UseSso": false, // Not supported
|
||||||
// TODO: Add support for Business Portal
|
|
||||||
// Upstream is moving Policies and SSO management outside of the web-vault to /portal
|
|
||||||
// For now they still have that code also in the web-vault, but they will remove it at some point.
|
|
||||||
// https://github.com/bitwarden/server/tree/master/bitwarden_license/src/
|
|
||||||
"UseBusinessPortal": false, // Disable BusinessPortal Button
|
|
||||||
"ProviderId": null,
|
"ProviderId": null,
|
||||||
"ProviderName": null,
|
"ProviderName": null,
|
||||||
|
// "KeyConnectorEnabled": false,
|
||||||
|
// "KeyConnectorUrl": null,
|
||||||
|
|
||||||
// TODO: Add support for Custom User Roles
|
// TODO: Add support for Custom User Roles
|
||||||
// See: https://bitwarden.com/help/article/user-types-access-control/#custom-role
|
// See: https://bitwarden.com/help/article/user-types-access-control/#custom-role
|
||||||
// "Permissions": {
|
// "Permissions": {
|
||||||
// "AccessBusinessPortal": false,
|
// "AccessEventLogs": false, // Not supported
|
||||||
// "AccessEventLogs": false,
|
|
||||||
// "AccessImportExport": false,
|
// "AccessImportExport": false,
|
||||||
// "AccessReports": false,
|
// "AccessReports": false,
|
||||||
// "ManageAllCollections": false,
|
// "ManageAllCollections": false,
|
||||||
|
// "CreateNewCollections": false,
|
||||||
|
// "EditAnyCollection": false,
|
||||||
|
// "DeleteAnyCollection": false,
|
||||||
// "ManageAssignedCollections": false,
|
// "ManageAssignedCollections": false,
|
||||||
|
// "editAssignedCollections": false,
|
||||||
|
// "deleteAssignedCollections": false,
|
||||||
// "ManageCiphers": false,
|
// "ManageCiphers": false,
|
||||||
// "ManageGroups": false,
|
// "ManageGroups": false, // Not supported
|
||||||
// "ManagePolicies": false,
|
// "ManagePolicies": false,
|
||||||
// "ManageResetPassword": false,
|
// "ManageResetPassword": false, // Not supported
|
||||||
// "ManageSso": false,
|
// "ManageSso": false, // Not supported
|
||||||
// "ManageUsers": false,
|
// "ManageUsers": false,
|
||||||
|
// "ManageScim": false, // Not supported (Not AGPLv3 Licensed)
|
||||||
// },
|
// },
|
||||||
|
|
||||||
"MaxStorageGb": 10, // The value doesn't matter, we don't check server-side
|
"MaxStorageGb": 10, // The value doesn't matter, we don't check server-side
|
||||||
|
|
||||||
// These are per user
|
// These are per user
|
||||||
|
"UserId": self.user_uuid,
|
||||||
"Key": self.akey,
|
"Key": self.akey,
|
||||||
"Status": self.status,
|
"Status": self.status,
|
||||||
"Type": self.atype,
|
"Type": self.atype,
|
||||||
@@ -325,13 +353,21 @@ impl UserOrganization {
|
|||||||
pub async fn to_json_user_details(&self, conn: &DbConn) -> Value {
|
pub async fn to_json_user_details(&self, conn: &DbConn) -> Value {
|
||||||
let user = User::find_by_uuid(&self.user_uuid, conn).await.unwrap();
|
let user = User::find_by_uuid(&self.user_uuid, conn).await.unwrap();
|
||||||
|
|
||||||
|
// Because BitWarden want the status to be -1 for revoked users we need to catch that here.
|
||||||
|
// We subtract/add a number so we can restore/activate the user to it's previouse state again.
|
||||||
|
let status = if self.status < UserOrgStatus::Revoked as i32 {
|
||||||
|
UserOrgStatus::Revoked as i32
|
||||||
|
} else {
|
||||||
|
self.status
|
||||||
|
};
|
||||||
|
|
||||||
json!({
|
json!({
|
||||||
"Id": self.uuid,
|
"Id": self.uuid,
|
||||||
"UserId": self.user_uuid,
|
"UserId": self.user_uuid,
|
||||||
"Name": user.name,
|
"Name": user.name,
|
||||||
"Email": user.email,
|
"Email": user.email,
|
||||||
|
|
||||||
"Status": self.status,
|
"Status": status,
|
||||||
"Type": self.atype,
|
"Type": self.atype,
|
||||||
"AccessAll": self.access_all,
|
"AccessAll": self.access_all,
|
||||||
|
|
||||||
@@ -365,11 +401,19 @@ impl UserOrganization {
|
|||||||
.collect()
|
.collect()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Because BitWarden want the status to be -1 for revoked users we need to catch that here.
|
||||||
|
// We subtract/add a number so we can restore/activate the user to it's previouse state again.
|
||||||
|
let status = if self.status < UserOrgStatus::Revoked as i32 {
|
||||||
|
UserOrgStatus::Revoked as i32
|
||||||
|
} else {
|
||||||
|
self.status
|
||||||
|
};
|
||||||
|
|
||||||
json!({
|
json!({
|
||||||
"Id": self.uuid,
|
"Id": self.uuid,
|
||||||
"UserId": self.user_uuid,
|
"UserId": self.user_uuid,
|
||||||
|
|
||||||
"Status": self.status,
|
"Status": status,
|
||||||
"Type": self.atype,
|
"Type": self.atype,
|
||||||
"AccessAll": self.access_all,
|
"AccessAll": self.access_all,
|
||||||
"Collections": coll_uuids,
|
"Collections": coll_uuids,
|
||||||
@@ -507,6 +551,18 @@ impl UserOrganization {
|
|||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn count_accepted_and_confirmed_by_user(user_uuid: &str, conn: &DbConn) -> i64 {
|
||||||
|
db_run! { conn: {
|
||||||
|
users_organizations::table
|
||||||
|
.filter(users_organizations::user_uuid.eq(user_uuid))
|
||||||
|
.filter(users_organizations::status.eq(UserOrgStatus::Accepted as i32))
|
||||||
|
.or_filter(users_organizations::status.eq(UserOrgStatus::Confirmed as i32))
|
||||||
|
.count()
|
||||||
|
.first::<i64>(conn)
|
||||||
|
.unwrap_or(0)
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn find_by_org(org_uuid: &str, conn: &DbConn) -> Vec<Self> {
|
pub async fn find_by_org(org_uuid: &str, conn: &DbConn) -> Vec<Self> {
|
||||||
db_run! { conn: {
|
db_run! { conn: {
|
||||||
users_organizations::table
|
users_organizations::table
|
||||||
@@ -527,16 +583,28 @@ impl UserOrganization {
|
|||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn find_by_org_and_type(org_uuid: &str, atype: i32, conn: &DbConn) -> Vec<Self> {
|
pub async fn find_by_org_and_type(org_uuid: &str, atype: UserOrgType, conn: &DbConn) -> Vec<Self> {
|
||||||
db_run! { conn: {
|
db_run! { conn: {
|
||||||
users_organizations::table
|
users_organizations::table
|
||||||
.filter(users_organizations::org_uuid.eq(org_uuid))
|
.filter(users_organizations::org_uuid.eq(org_uuid))
|
||||||
.filter(users_organizations::atype.eq(atype))
|
.filter(users_organizations::atype.eq(atype as i32))
|
||||||
.load::<UserOrganizationDb>(conn)
|
.load::<UserOrganizationDb>(conn)
|
||||||
.expect("Error loading user organizations").from_db()
|
.expect("Error loading user organizations").from_db()
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn count_confirmed_by_org_and_type(org_uuid: &str, atype: UserOrgType, conn: &DbConn) -> i64 {
|
||||||
|
db_run! { conn: {
|
||||||
|
users_organizations::table
|
||||||
|
.filter(users_organizations::org_uuid.eq(org_uuid))
|
||||||
|
.filter(users_organizations::atype.eq(atype as i32))
|
||||||
|
.filter(users_organizations::status.eq(UserOrgStatus::Confirmed as i32))
|
||||||
|
.count()
|
||||||
|
.first::<i64>(conn)
|
||||||
|
.unwrap_or(0)
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn find_by_user_and_org(user_uuid: &str, org_uuid: &str, conn: &DbConn) -> Option<Self> {
|
pub async fn find_by_user_and_org(user_uuid: &str, org_uuid: &str, conn: &DbConn) -> Option<Self> {
|
||||||
db_run! { conn: {
|
db_run! { conn: {
|
||||||
users_organizations::table
|
users_organizations::table
|
||||||
|
@@ -275,11 +275,11 @@ impl User {
|
|||||||
|
|
||||||
pub async fn delete(self, conn: &DbConn) -> EmptyResult {
|
pub async fn delete(self, conn: &DbConn) -> EmptyResult {
|
||||||
for user_org in UserOrganization::find_confirmed_by_user(&self.uuid, conn).await {
|
for user_org in UserOrganization::find_confirmed_by_user(&self.uuid, conn).await {
|
||||||
if user_org.atype == UserOrgType::Owner {
|
if user_org.atype == UserOrgType::Owner
|
||||||
let owner_type = UserOrgType::Owner as i32;
|
&& UserOrganization::count_confirmed_by_org_and_type(&user_org.org_uuid, UserOrgType::Owner, conn).await
|
||||||
if UserOrganization::find_by_org_and_type(&user_org.org_uuid, owner_type, conn).await.len() <= 1 {
|
<= 1
|
||||||
err!("Can't delete last owner")
|
{
|
||||||
}
|
err!("Can't delete last owner")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -303,6 +303,10 @@ async fn check_data_folder() {
|
|||||||
}
|
}
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
if !path.is_dir() {
|
||||||
|
error!("Data folder '{}' is not a directory.", data_folder);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
if is_running_in_docker()
|
if is_running_in_docker()
|
||||||
&& std::env::var("I_REALLY_WANT_VOLATILE_STORAGE").is_err()
|
&& std::env::var("I_REALLY_WANT_VOLATILE_STORAGE").is_err()
|
||||||
@@ -425,6 +429,8 @@ async fn launch_rocket(pool: db::DbPool, extra_debug: bool) -> Result<(), Error>
|
|||||||
.mount([basepath, "/identity"].concat(), api::identity_routes())
|
.mount([basepath, "/identity"].concat(), api::identity_routes())
|
||||||
.mount([basepath, "/icons"].concat(), api::icons_routes())
|
.mount([basepath, "/icons"].concat(), api::icons_routes())
|
||||||
.mount([basepath, "/notifications"].concat(), api::notifications_routes())
|
.mount([basepath, "/notifications"].concat(), api::notifications_routes())
|
||||||
|
.register([basepath, "/"].concat(), api::web_catchers())
|
||||||
|
.register([basepath, "/api"].concat(), api::core_catchers())
|
||||||
.manage(pool)
|
.manage(pool)
|
||||||
.manage(api::start_notification_server())
|
.manage(api::start_notification_server())
|
||||||
.attach(util::AppHeaders())
|
.attach(util::AppHeaders())
|
||||||
|
1223
src/static/scripts/bootstrap.css
vendored
1223
src/static/scripts/bootstrap.css
vendored
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
|||||||
/*!
|
/*!
|
||||||
* jQuery JavaScript Library v3.6.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/Tween,-effects/animatedSelector
|
* jQuery JavaScript Library v3.6.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/Tween,-effects/animatedSelector
|
||||||
* https://jquery.com/
|
* https://jquery.com/
|
||||||
*
|
*
|
||||||
* Includes Sizzle.js
|
* Includes Sizzle.js
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
* Released under the MIT license
|
* Released under the MIT license
|
||||||
* https://jquery.org/license
|
* https://jquery.org/license
|
||||||
*
|
*
|
||||||
* Date: 2021-03-02T17:08Z
|
* Date: 2022-08-26T17:52Z
|
||||||
*/
|
*/
|
||||||
( function( global, factory ) {
|
( function( global, factory ) {
|
||||||
|
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
// (such as Node.js), expose a factory as module.exports.
|
// (such as Node.js), expose a factory as module.exports.
|
||||||
// This accentuates the need for the creation of a real `window`.
|
// This accentuates the need for the creation of a real `window`.
|
||||||
// e.g. var jQuery = require("jquery")(window);
|
// e.g. var jQuery = require("jquery")(window);
|
||||||
// See ticket #14549 for more info.
|
// See ticket trac-14549 for more info.
|
||||||
module.exports = global.document ?
|
module.exports = global.document ?
|
||||||
factory( global, true ) :
|
factory( global, true ) :
|
||||||
function( w ) {
|
function( w ) {
|
||||||
@@ -151,7 +151,7 @@ function toType( obj ) {
|
|||||||
|
|
||||||
|
|
||||||
var
|
var
|
||||||
version = "3.6.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/Tween,-effects/animatedSelector",
|
version = "3.6.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/Tween,-effects/animatedSelector",
|
||||||
|
|
||||||
// Define a local copy of jQuery
|
// Define a local copy of jQuery
|
||||||
jQuery = function( selector, context ) {
|
jQuery = function( selector, context ) {
|
||||||
@@ -3129,8 +3129,8 @@ jQuery.fn.extend( {
|
|||||||
var rootjQuery,
|
var rootjQuery,
|
||||||
|
|
||||||
// A simple way to check for HTML strings
|
// A simple way to check for HTML strings
|
||||||
// Prioritize #id over <tag> to avoid XSS via location.hash (#9521)
|
// Prioritize #id over <tag> to avoid XSS via location.hash (trac-9521)
|
||||||
// Strict HTML recognition (#11290: must start with <)
|
// Strict HTML recognition (trac-11290: must start with <)
|
||||||
// Shortcut simple #id case for speed
|
// Shortcut simple #id case for speed
|
||||||
rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/,
|
rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/,
|
||||||
|
|
||||||
@@ -4087,7 +4087,7 @@ jQuery.extend( {
|
|||||||
isReady: false,
|
isReady: false,
|
||||||
|
|
||||||
// A counter to track how many items to wait for before
|
// A counter to track how many items to wait for before
|
||||||
// the ready event fires. See #6781
|
// the ready event fires. See trac-6781
|
||||||
readyWait: 1,
|
readyWait: 1,
|
||||||
|
|
||||||
// Handle when the DOM is ready
|
// Handle when the DOM is ready
|
||||||
@@ -4215,7 +4215,7 @@ function fcamelCase( _all, letter ) {
|
|||||||
|
|
||||||
// Convert dashed to camelCase; used by the css and data modules
|
// Convert dashed to camelCase; used by the css and data modules
|
||||||
// Support: IE <=9 - 11, Edge 12 - 15
|
// Support: IE <=9 - 11, Edge 12 - 15
|
||||||
// Microsoft forgot to hump their vendor prefix (#9572)
|
// Microsoft forgot to hump their vendor prefix (trac-9572)
|
||||||
function camelCase( string ) {
|
function camelCase( string ) {
|
||||||
return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase );
|
return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase );
|
||||||
}
|
}
|
||||||
@@ -4251,7 +4251,7 @@ Data.prototype = {
|
|||||||
value = {};
|
value = {};
|
||||||
|
|
||||||
// We can accept data for non-element nodes in modern browsers,
|
// We can accept data for non-element nodes in modern browsers,
|
||||||
// but we should not, see #8335.
|
// but we should not, see trac-8335.
|
||||||
// Always return an empty object.
|
// Always return an empty object.
|
||||||
if ( acceptData( owner ) ) {
|
if ( acceptData( owner ) ) {
|
||||||
|
|
||||||
@@ -4490,7 +4490,7 @@ jQuery.fn.extend( {
|
|||||||
while ( i-- ) {
|
while ( i-- ) {
|
||||||
|
|
||||||
// Support: IE 11 only
|
// Support: IE 11 only
|
||||||
// The attrs elements can be null (#14894)
|
// The attrs elements can be null (trac-14894)
|
||||||
if ( attrs[ i ] ) {
|
if ( attrs[ i ] ) {
|
||||||
name = attrs[ i ].name;
|
name = attrs[ i ].name;
|
||||||
if ( name.indexOf( "data-" ) === 0 ) {
|
if ( name.indexOf( "data-" ) === 0 ) {
|
||||||
@@ -4913,9 +4913,9 @@ var rscriptType = ( /^$|^module$|\/(?:java|ecma)script/i );
|
|||||||
input = document.createElement( "input" );
|
input = document.createElement( "input" );
|
||||||
|
|
||||||
// Support: Android 4.0 - 4.3 only
|
// Support: Android 4.0 - 4.3 only
|
||||||
// Check state lost if the name is set (#11217)
|
// Check state lost if the name is set (trac-11217)
|
||||||
// Support: Windows Web Apps (WWA)
|
// Support: Windows Web Apps (WWA)
|
||||||
// `name` and `type` must use .setAttribute for WWA (#14901)
|
// `name` and `type` must use .setAttribute for WWA (trac-14901)
|
||||||
input.setAttribute( "type", "radio" );
|
input.setAttribute( "type", "radio" );
|
||||||
input.setAttribute( "checked", "checked" );
|
input.setAttribute( "checked", "checked" );
|
||||||
input.setAttribute( "name", "t" );
|
input.setAttribute( "name", "t" );
|
||||||
@@ -4939,7 +4939,7 @@ var rscriptType = ( /^$|^module$|\/(?:java|ecma)script/i );
|
|||||||
} )();
|
} )();
|
||||||
|
|
||||||
|
|
||||||
// We have to close these tags to support XHTML (#13200)
|
// We have to close these tags to support XHTML (trac-13200)
|
||||||
var wrapMap = {
|
var wrapMap = {
|
||||||
|
|
||||||
// XHTML parsers do not magically insert elements in the
|
// XHTML parsers do not magically insert elements in the
|
||||||
@@ -4965,7 +4965,7 @@ if ( !support.option ) {
|
|||||||
function getAll( context, tag ) {
|
function getAll( context, tag ) {
|
||||||
|
|
||||||
// Support: IE <=9 - 11 only
|
// Support: IE <=9 - 11 only
|
||||||
// Use typeof to avoid zero-argument method invocation on host objects (#15151)
|
// Use typeof to avoid zero-argument method invocation on host objects (trac-15151)
|
||||||
var ret;
|
var ret;
|
||||||
|
|
||||||
if ( typeof context.getElementsByTagName !== "undefined" ) {
|
if ( typeof context.getElementsByTagName !== "undefined" ) {
|
||||||
@@ -5048,7 +5048,7 @@ function buildFragment( elems, context, scripts, selection, ignored ) {
|
|||||||
// Remember the top-level container
|
// Remember the top-level container
|
||||||
tmp = fragment.firstChild;
|
tmp = fragment.firstChild;
|
||||||
|
|
||||||
// Ensure the created nodes are orphaned (#12392)
|
// Ensure the created nodes are orphaned (trac-12392)
|
||||||
tmp.textContent = "";
|
tmp.textContent = "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5469,15 +5469,15 @@ jQuery.event = {
|
|||||||
|
|
||||||
for ( ; cur !== this; cur = cur.parentNode || this ) {
|
for ( ; cur !== this; cur = cur.parentNode || this ) {
|
||||||
|
|
||||||
// Don't check non-elements (#13208)
|
// Don't check non-elements (trac-13208)
|
||||||
// Don't process clicks on disabled elements (#6911, #8165, #11382, #11764)
|
// Don't process clicks on disabled elements (trac-6911, trac-8165, trac-11382, trac-11764)
|
||||||
if ( cur.nodeType === 1 && !( event.type === "click" && cur.disabled === true ) ) {
|
if ( cur.nodeType === 1 && !( event.type === "click" && cur.disabled === true ) ) {
|
||||||
matchedHandlers = [];
|
matchedHandlers = [];
|
||||||
matchedSelectors = {};
|
matchedSelectors = {};
|
||||||
for ( i = 0; i < delegateCount; i++ ) {
|
for ( i = 0; i < delegateCount; i++ ) {
|
||||||
handleObj = handlers[ i ];
|
handleObj = handlers[ i ];
|
||||||
|
|
||||||
// Don't conflict with Object.prototype properties (#13203)
|
// Don't conflict with Object.prototype properties (trac-13203)
|
||||||
sel = handleObj.selector + " ";
|
sel = handleObj.selector + " ";
|
||||||
|
|
||||||
if ( matchedSelectors[ sel ] === undefined ) {
|
if ( matchedSelectors[ sel ] === undefined ) {
|
||||||
@@ -5731,7 +5731,7 @@ jQuery.Event = function( src, props ) {
|
|||||||
|
|
||||||
// Create target properties
|
// Create target properties
|
||||||
// Support: Safari <=6 - 7 only
|
// Support: Safari <=6 - 7 only
|
||||||
// Target should not be a text node (#504, #13143)
|
// Target should not be a text node (trac-504, trac-13143)
|
||||||
this.target = ( src.target && src.target.nodeType === 3 ) ?
|
this.target = ( src.target && src.target.nodeType === 3 ) ?
|
||||||
src.target.parentNode :
|
src.target.parentNode :
|
||||||
src.target;
|
src.target;
|
||||||
@@ -5854,10 +5854,10 @@ jQuery.each( { focus: "focusin", blur: "focusout" }, function( type, delegateTyp
|
|||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
|
||||||
// Suppress native focus or blur as it's already being fired
|
// Suppress native focus or blur if we're currently inside
|
||||||
// in leverageNative.
|
// a leveraged native-event stack
|
||||||
_default: function() {
|
_default: function( event ) {
|
||||||
return true;
|
return dataPriv.get( event.target, type );
|
||||||
},
|
},
|
||||||
|
|
||||||
delegateType: delegateType
|
delegateType: delegateType
|
||||||
@@ -5956,7 +5956,8 @@ var
|
|||||||
|
|
||||||
// checked="checked" or checked
|
// checked="checked" or checked
|
||||||
rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i,
|
rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i,
|
||||||
rcleanScript = /^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g;
|
|
||||||
|
rcleanScript = /^\s*<!\[CDATA\[|\]\]>\s*$/g;
|
||||||
|
|
||||||
// Prefer a tbody over its parent table for containing new rows
|
// Prefer a tbody over its parent table for containing new rows
|
||||||
function manipulationTarget( elem, content ) {
|
function manipulationTarget( elem, content ) {
|
||||||
@@ -6070,7 +6071,7 @@ function domManip( collection, args, callback, ignored ) {
|
|||||||
|
|
||||||
// Use the original fragment for the last item
|
// Use the original fragment for the last item
|
||||||
// instead of the first because it can end up
|
// instead of the first because it can end up
|
||||||
// being emptied incorrectly in certain situations (#8070).
|
// being emptied incorrectly in certain situations (trac-8070).
|
||||||
for ( ; i < l; i++ ) {
|
for ( ; i < l; i++ ) {
|
||||||
node = fragment;
|
node = fragment;
|
||||||
|
|
||||||
@@ -6111,6 +6112,12 @@ function domManip( collection, args, callback, ignored ) {
|
|||||||
}, doc );
|
}, doc );
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
|
// Unwrap a CDATA section containing script contents. This shouldn't be
|
||||||
|
// needed as in XML documents they're already not visible when
|
||||||
|
// inspecting element contents and in HTML documents they have no
|
||||||
|
// meaning but we're preserving that logic for backwards compatibility.
|
||||||
|
// This will be removed completely in 4.0. See gh-4904.
|
||||||
DOMEval( node.textContent.replace( rcleanScript, "" ), node, doc );
|
DOMEval( node.textContent.replace( rcleanScript, "" ), node, doc );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6393,9 +6400,12 @@ jQuery.each( {
|
|||||||
} );
|
} );
|
||||||
var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" );
|
var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" );
|
||||||
|
|
||||||
|
var rcustomProp = /^--/;
|
||||||
|
|
||||||
|
|
||||||
var getStyles = function( elem ) {
|
var getStyles = function( elem ) {
|
||||||
|
|
||||||
// Support: IE <=11 only, Firefox <=30 (#15098, #14150)
|
// Support: IE <=11 only, Firefox <=30 (trac-15098, trac-14150)
|
||||||
// IE throws on elements created in popups
|
// IE throws on elements created in popups
|
||||||
// FF meanwhile throws on frame elements through "defaultView.getComputedStyle"
|
// FF meanwhile throws on frame elements through "defaultView.getComputedStyle"
|
||||||
var view = elem.ownerDocument.defaultView;
|
var view = elem.ownerDocument.defaultView;
|
||||||
@@ -6430,6 +6440,15 @@ var swap = function( elem, options, callback ) {
|
|||||||
|
|
||||||
var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" );
|
var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" );
|
||||||
|
|
||||||
|
var whitespace = "[\\x20\\t\\r\\n\\f]";
|
||||||
|
|
||||||
|
|
||||||
|
var rtrimCSS = new RegExp(
|
||||||
|
"^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$",
|
||||||
|
"g"
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
( function() {
|
( function() {
|
||||||
@@ -6495,7 +6514,7 @@ var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" );
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Support: IE <=9 - 11 only
|
// Support: IE <=9 - 11 only
|
||||||
// Style of cloned element affects source element cloned (#8908)
|
// Style of cloned element affects source element cloned (trac-8908)
|
||||||
div.style.backgroundClip = "content-box";
|
div.style.backgroundClip = "content-box";
|
||||||
div.cloneNode( true ).style.backgroundClip = "";
|
div.cloneNode( true ).style.backgroundClip = "";
|
||||||
support.clearCloneStyle = div.style.backgroundClip === "content-box";
|
support.clearCloneStyle = div.style.backgroundClip === "content-box";
|
||||||
@@ -6575,6 +6594,7 @@ var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" );
|
|||||||
|
|
||||||
function curCSS( elem, name, computed ) {
|
function curCSS( elem, name, computed ) {
|
||||||
var width, minWidth, maxWidth, ret,
|
var width, minWidth, maxWidth, ret,
|
||||||
|
isCustomProp = rcustomProp.test( name ),
|
||||||
|
|
||||||
// Support: Firefox 51+
|
// Support: Firefox 51+
|
||||||
// Retrieving style before computed somehow
|
// Retrieving style before computed somehow
|
||||||
@@ -6585,11 +6605,22 @@ function curCSS( elem, name, computed ) {
|
|||||||
computed = computed || getStyles( elem );
|
computed = computed || getStyles( elem );
|
||||||
|
|
||||||
// getPropertyValue is needed for:
|
// getPropertyValue is needed for:
|
||||||
// .css('filter') (IE 9 only, #12537)
|
// .css('filter') (IE 9 only, trac-12537)
|
||||||
// .css('--customProperty) (#3144)
|
// .css('--customProperty) (gh-3144)
|
||||||
if ( computed ) {
|
if ( computed ) {
|
||||||
ret = computed.getPropertyValue( name ) || computed[ name ];
|
ret = computed.getPropertyValue( name ) || computed[ name ];
|
||||||
|
|
||||||
|
// trim whitespace for custom property (issue gh-4926)
|
||||||
|
if ( isCustomProp ) {
|
||||||
|
|
||||||
|
// rtrim treats U+000D CARRIAGE RETURN and U+000C FORM FEED
|
||||||
|
// as whitespace while CSS does not, but this is not a problem
|
||||||
|
// because CSS preprocessing replaces them with U+000A LINE FEED
|
||||||
|
// (which *is* CSS whitespace)
|
||||||
|
// https://www.w3.org/TR/css-syntax-3/#input-preprocessing
|
||||||
|
ret = ret.replace( rtrimCSS, "$1" );
|
||||||
|
}
|
||||||
|
|
||||||
if ( ret === "" && !isAttached( elem ) ) {
|
if ( ret === "" && !isAttached( elem ) ) {
|
||||||
ret = jQuery.style( elem, name );
|
ret = jQuery.style( elem, name );
|
||||||
}
|
}
|
||||||
@@ -6685,7 +6716,6 @@ var
|
|||||||
// except "table", "table-cell", or "table-caption"
|
// except "table", "table-cell", or "table-caption"
|
||||||
// See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display
|
// See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display
|
||||||
rdisplayswap = /^(none|table(?!-c[ea]).+)/,
|
rdisplayswap = /^(none|table(?!-c[ea]).+)/,
|
||||||
rcustomProp = /^--/,
|
|
||||||
cssShow = { position: "absolute", visibility: "hidden", display: "block" },
|
cssShow = { position: "absolute", visibility: "hidden", display: "block" },
|
||||||
cssNormalTransform = {
|
cssNormalTransform = {
|
||||||
letterSpacing: "0",
|
letterSpacing: "0",
|
||||||
@@ -6921,15 +6951,15 @@ jQuery.extend( {
|
|||||||
if ( value !== undefined ) {
|
if ( value !== undefined ) {
|
||||||
type = typeof value;
|
type = typeof value;
|
||||||
|
|
||||||
// Convert "+=" or "-=" to relative numbers (#7345)
|
// Convert "+=" or "-=" to relative numbers (trac-7345)
|
||||||
if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) {
|
if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) {
|
||||||
value = adjustCSS( elem, name, ret );
|
value = adjustCSS( elem, name, ret );
|
||||||
|
|
||||||
// Fixes bug #9237
|
// Fixes bug trac-9237
|
||||||
type = "number";
|
type = "number";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure that null and NaN values aren't set (#7116)
|
// Make sure that null and NaN values aren't set (trac-7116)
|
||||||
if ( value == null || value !== value ) {
|
if ( value == null || value !== value ) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -7149,7 +7179,6 @@ jQuery.fn.extend( {
|
|||||||
|
|
||||||
|
|
||||||
// Based off of the plugin by Clint Helfers, with permission.
|
// Based off of the plugin by Clint Helfers, with permission.
|
||||||
// https://web.archive.org/web/20100324014747/http://blindsignals.com/index.php/2009/07/jquery-delay/
|
|
||||||
jQuery.fn.delay = function( time, type ) {
|
jQuery.fn.delay = function( time, type ) {
|
||||||
time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time;
|
time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time;
|
||||||
type = type || "fx";
|
type = type || "fx";
|
||||||
@@ -7374,8 +7403,7 @@ jQuery.extend( {
|
|||||||
// Support: IE <=9 - 11 only
|
// Support: IE <=9 - 11 only
|
||||||
// elem.tabIndex doesn't always return the
|
// elem.tabIndex doesn't always return the
|
||||||
// correct value when it hasn't been explicitly set
|
// correct value when it hasn't been explicitly set
|
||||||
// https://web.archive.org/web/20141116233347/http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/
|
// Use proper attribute retrieval (trac-12072)
|
||||||
// Use proper attribute retrieval(#12072)
|
|
||||||
var tabindex = jQuery.find.attr( elem, "tabindex" );
|
var tabindex = jQuery.find.attr( elem, "tabindex" );
|
||||||
|
|
||||||
if ( tabindex ) {
|
if ( tabindex ) {
|
||||||
@@ -7479,8 +7507,7 @@ function classesToArray( value ) {
|
|||||||
|
|
||||||
jQuery.fn.extend( {
|
jQuery.fn.extend( {
|
||||||
addClass: function( value ) {
|
addClass: function( value ) {
|
||||||
var classes, elem, cur, curValue, clazz, j, finalValue,
|
var classNames, cur, curValue, className, i, finalValue;
|
||||||
i = 0;
|
|
||||||
|
|
||||||
if ( isFunction( value ) ) {
|
if ( isFunction( value ) ) {
|
||||||
return this.each( function( j ) {
|
return this.each( function( j ) {
|
||||||
@@ -7488,36 +7515,35 @@ jQuery.fn.extend( {
|
|||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
classes = classesToArray( value );
|
classNames = classesToArray( value );
|
||||||
|
|
||||||
if ( classes.length ) {
|
if ( classNames.length ) {
|
||||||
while ( ( elem = this[ i++ ] ) ) {
|
return this.each( function() {
|
||||||
curValue = getClass( elem );
|
curValue = getClass( this );
|
||||||
cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " );
|
cur = this.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " );
|
||||||
|
|
||||||
if ( cur ) {
|
if ( cur ) {
|
||||||
j = 0;
|
for ( i = 0; i < classNames.length; i++ ) {
|
||||||
while ( ( clazz = classes[ j++ ] ) ) {
|
className = classNames[ i ];
|
||||||
if ( cur.indexOf( " " + clazz + " " ) < 0 ) {
|
if ( cur.indexOf( " " + className + " " ) < 0 ) {
|
||||||
cur += clazz + " ";
|
cur += className + " ";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only assign if different to avoid unneeded rendering.
|
// Only assign if different to avoid unneeded rendering.
|
||||||
finalValue = stripAndCollapse( cur );
|
finalValue = stripAndCollapse( cur );
|
||||||
if ( curValue !== finalValue ) {
|
if ( curValue !== finalValue ) {
|
||||||
elem.setAttribute( "class", finalValue );
|
this.setAttribute( "class", finalValue );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
|
|
||||||
removeClass: function( value ) {
|
removeClass: function( value ) {
|
||||||
var classes, elem, cur, curValue, clazz, j, finalValue,
|
var classNames, cur, curValue, className, i, finalValue;
|
||||||
i = 0;
|
|
||||||
|
|
||||||
if ( isFunction( value ) ) {
|
if ( isFunction( value ) ) {
|
||||||
return this.each( function( j ) {
|
return this.each( function( j ) {
|
||||||
@@ -7529,45 +7555,42 @@ jQuery.fn.extend( {
|
|||||||
return this.attr( "class", "" );
|
return this.attr( "class", "" );
|
||||||
}
|
}
|
||||||
|
|
||||||
classes = classesToArray( value );
|
classNames = classesToArray( value );
|
||||||
|
|
||||||
if ( classes.length ) {
|
if ( classNames.length ) {
|
||||||
while ( ( elem = this[ i++ ] ) ) {
|
return this.each( function() {
|
||||||
curValue = getClass( elem );
|
curValue = getClass( this );
|
||||||
|
|
||||||
// This expression is here for better compressibility (see addClass)
|
// This expression is here for better compressibility (see addClass)
|
||||||
cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " );
|
cur = this.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " );
|
||||||
|
|
||||||
if ( cur ) {
|
if ( cur ) {
|
||||||
j = 0;
|
for ( i = 0; i < classNames.length; i++ ) {
|
||||||
while ( ( clazz = classes[ j++ ] ) ) {
|
className = classNames[ i ];
|
||||||
|
|
||||||
// Remove *all* instances
|
// Remove *all* instances
|
||||||
while ( cur.indexOf( " " + clazz + " " ) > -1 ) {
|
while ( cur.indexOf( " " + className + " " ) > -1 ) {
|
||||||
cur = cur.replace( " " + clazz + " ", " " );
|
cur = cur.replace( " " + className + " ", " " );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only assign if different to avoid unneeded rendering.
|
// Only assign if different to avoid unneeded rendering.
|
||||||
finalValue = stripAndCollapse( cur );
|
finalValue = stripAndCollapse( cur );
|
||||||
if ( curValue !== finalValue ) {
|
if ( curValue !== finalValue ) {
|
||||||
elem.setAttribute( "class", finalValue );
|
this.setAttribute( "class", finalValue );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
|
|
||||||
toggleClass: function( value, stateVal ) {
|
toggleClass: function( value, stateVal ) {
|
||||||
var type = typeof value,
|
var classNames, className, i, self,
|
||||||
|
type = typeof value,
|
||||||
isValidValue = type === "string" || Array.isArray( value );
|
isValidValue = type === "string" || Array.isArray( value );
|
||||||
|
|
||||||
if ( typeof stateVal === "boolean" && isValidValue ) {
|
|
||||||
return stateVal ? this.addClass( value ) : this.removeClass( value );
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( isFunction( value ) ) {
|
if ( isFunction( value ) ) {
|
||||||
return this.each( function( i ) {
|
return this.each( function( i ) {
|
||||||
jQuery( this ).toggleClass(
|
jQuery( this ).toggleClass(
|
||||||
@@ -7577,17 +7600,20 @@ jQuery.fn.extend( {
|
|||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.each( function() {
|
if ( typeof stateVal === "boolean" && isValidValue ) {
|
||||||
var className, i, self, classNames;
|
return stateVal ? this.addClass( value ) : this.removeClass( value );
|
||||||
|
}
|
||||||
|
|
||||||
|
classNames = classesToArray( value );
|
||||||
|
|
||||||
|
return this.each( function() {
|
||||||
if ( isValidValue ) {
|
if ( isValidValue ) {
|
||||||
|
|
||||||
// Toggle individual class names
|
// Toggle individual class names
|
||||||
i = 0;
|
|
||||||
self = jQuery( this );
|
self = jQuery( this );
|
||||||
classNames = classesToArray( value );
|
|
||||||
|
|
||||||
while ( ( className = classNames[ i++ ] ) ) {
|
for ( i = 0; i < classNames.length; i++ ) {
|
||||||
|
className = classNames[ i ];
|
||||||
|
|
||||||
// Check each className given, space separated list
|
// Check each className given, space separated list
|
||||||
if ( self.hasClass( className ) ) {
|
if ( self.hasClass( className ) ) {
|
||||||
@@ -7721,7 +7747,7 @@ jQuery.extend( {
|
|||||||
val :
|
val :
|
||||||
|
|
||||||
// Support: IE <=10 - 11 only
|
// Support: IE <=10 - 11 only
|
||||||
// option.text throws exceptions (#14686, #14858)
|
// option.text throws exceptions (trac-14686, trac-14858)
|
||||||
// Strip and collapse whitespace
|
// Strip and collapse whitespace
|
||||||
// https://html.spec.whatwg.org/#strip-and-collapse-whitespace
|
// https://html.spec.whatwg.org/#strip-and-collapse-whitespace
|
||||||
stripAndCollapse( jQuery.text( elem ) );
|
stripAndCollapse( jQuery.text( elem ) );
|
||||||
@@ -7748,7 +7774,7 @@ jQuery.extend( {
|
|||||||
option = options[ i ];
|
option = options[ i ];
|
||||||
|
|
||||||
// Support: IE <=9 only
|
// Support: IE <=9 only
|
||||||
// IE8-9 doesn't update selected after form reset (#2551)
|
// IE8-9 doesn't update selected after form reset (trac-2551)
|
||||||
if ( ( option.selected || i === index ) &&
|
if ( ( option.selected || i === index ) &&
|
||||||
|
|
||||||
// Don't return options that are disabled or in a disabled optgroup
|
// Don't return options that are disabled or in a disabled optgroup
|
||||||
@@ -7891,8 +7917,8 @@ jQuery.extend( jQuery.event, {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine event propagation path in advance, per W3C events spec (#9951)
|
// Determine event propagation path in advance, per W3C events spec (trac-9951)
|
||||||
// Bubble up to document, then to window; watch for a global ownerDocument var (#9724)
|
// Bubble up to document, then to window; watch for a global ownerDocument var (trac-9724)
|
||||||
if ( !onlyHandlers && !special.noBubble && !isWindow( elem ) ) {
|
if ( !onlyHandlers && !special.noBubble && !isWindow( elem ) ) {
|
||||||
|
|
||||||
bubbleType = special.delegateType || type;
|
bubbleType = special.delegateType || type;
|
||||||
@@ -7944,7 +7970,7 @@ jQuery.extend( jQuery.event, {
|
|||||||
acceptData( elem ) ) {
|
acceptData( elem ) ) {
|
||||||
|
|
||||||
// Call a native DOM method on the target with the same name as the event.
|
// Call a native DOM method on the target with the same name as the event.
|
||||||
// Don't do default actions on window, that's where global variables be (#6170)
|
// Don't do default actions on window, that's where global variables be (trac-6170)
|
||||||
if ( ontype && isFunction( elem[ type ] ) && !isWindow( elem ) ) {
|
if ( ontype && isFunction( elem[ type ] ) && !isWindow( elem ) ) {
|
||||||
|
|
||||||
// Don't re-trigger an onFOO event when we call its FOO() method
|
// Don't re-trigger an onFOO event when we call its FOO() method
|
||||||
@@ -8654,7 +8680,9 @@ jQuery.each(
|
|||||||
|
|
||||||
// Support: Android <=4.0 only
|
// Support: Android <=4.0 only
|
||||||
// Make sure we trim BOM and NBSP
|
// Make sure we trim BOM and NBSP
|
||||||
var rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;
|
// Require that the "whitespace run" starts from a non-whitespace
|
||||||
|
// to avoid O(N^2) behavior when the engine would try matching "\s+$" at each space position.
|
||||||
|
var rtrim = /^[\s\uFEFF\xA0]+|([^\s\uFEFF\xA0])[\s\uFEFF\xA0]+$/g;
|
||||||
|
|
||||||
// Bind a function to a context, optionally partially applying any
|
// Bind a function to a context, optionally partially applying any
|
||||||
// arguments.
|
// arguments.
|
||||||
@@ -8721,7 +8749,7 @@ jQuery.isNumeric = function( obj ) {
|
|||||||
jQuery.trim = function( text ) {
|
jQuery.trim = function( text ) {
|
||||||
return text == null ?
|
return text == null ?
|
||||||
"" :
|
"" :
|
||||||
( text + "" ).replace( rtrim, "" );
|
( text + "" ).replace( rtrim, "$1" );
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@@ -8769,8 +8797,8 @@ jQuery.noConflict = function( deep ) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Expose jQuery and $ identifiers, even in AMD
|
// Expose jQuery and $ identifiers, even in AMD
|
||||||
// (#7102#comment:10, https://github.com/jquery/jquery/pull/557)
|
// (trac-7102#comment:10, https://github.com/jquery/jquery/pull/557)
|
||||||
// and CommonJS for browser emulators (#13566)
|
// and CommonJS for browser emulators (trac-13566)
|
||||||
if ( typeof noGlobal === "undefined" ) {
|
if ( typeof noGlobal === "undefined" ) {
|
||||||
window.jQuery = window.$ = jQuery;
|
window.jQuery = window.$ = jQuery;
|
||||||
}
|
}
|
@@ -140,8 +140,8 @@
|
|||||||
<span><b>Server:</b> {{page_data.server_time_local}}</span>
|
<span><b>Server:</b> {{page_data.server_time_local}}</span>
|
||||||
</dd>
|
</dd>
|
||||||
<dt class="col-sm-5">Date & Time (UTC)
|
<dt class="col-sm-5">Date & Time (UTC)
|
||||||
<span class="badge bg-success d-none" id="time-success" title="Time offsets seem to be correct.">Ok</span>
|
<span class="badge bg-success d-none" id="time-success" title="Server and browser times are within 30 seconds of each other.">Ok</span>
|
||||||
<span class="badge bg-danger d-none" id="time-warning" title="Time offsets are too mouch at drift.">Error</span>
|
<span class="badge bg-danger d-none" id="time-warning" title="Server and browser times are more than 30 seconds apart.">Error</span>
|
||||||
</dt>
|
</dt>
|
||||||
<dd class="col-sm-7">
|
<dd class="col-sm-7">
|
||||||
<span id="time-server" class="d-block"><b>Server:</b> <span id="time-server-string">{{page_data.server_time}}</span></span>
|
<span id="time-server" class="d-block"><b>Server:</b> <span id="time-server-string">{{page_data.server_time}}</span></span>
|
||||||
@@ -151,7 +151,7 @@
|
|||||||
<dt class="col-sm-5">Domain configuration
|
<dt class="col-sm-5">Domain configuration
|
||||||
<span class="badge bg-success d-none" id="domain-success" title="The domain variable matches the browser location and seems to be configured correctly.">Match</span>
|
<span class="badge bg-success d-none" id="domain-success" title="The domain variable matches the browser location and seems to be configured correctly.">Match</span>
|
||||||
<span class="badge bg-danger d-none" id="domain-warning" title="The domain variable does not match the browser location.
The domain variable does not seem to be configured correctly.
Some features may not work as expected!">No Match</span>
|
<span class="badge bg-danger d-none" id="domain-warning" title="The domain variable does not match the browser location.
The domain variable does not seem to be configured correctly.
Some features may not work as expected!">No Match</span>
|
||||||
<span class="badge bg-success d-none" id="https-success" title="Configurued to use HTTPS">HTTPS</span>
|
<span class="badge bg-success d-none" id="https-success" title="Configured to use HTTPS">HTTPS</span>
|
||||||
<span class="badge bg-danger d-none" id="https-warning" title="Not configured to use HTTPS.
Some features may not work as expected!">No HTTPS</span>
|
<span class="badge bg-danger d-none" id="https-warning" title="Not configured to use HTTPS.
Some features may not work as expected!">No HTTPS</span>
|
||||||
</dt>
|
</dt>
|
||||||
<dd class="col-sm-7">
|
<dd class="col-sm-7">
|
||||||
@@ -168,8 +168,8 @@
|
|||||||
<dl class="row">
|
<dl class="row">
|
||||||
<dd class="col-sm-12">
|
<dd class="col-sm-12">
|
||||||
If you need support please check the following links first before you create a new issue:
|
If you need support please check the following links first before you create a new issue:
|
||||||
<a href="https://vaultwarden.discourse.group/" target="_blank" rel="noreferrer">Vaultwarden Forum</a>
|
<a href="https://vaultwarden.discourse.group/" target="_blank" rel="noreferrer noopener">Vaultwarden Forum</a>
|
||||||
| <a href="https://github.com/dani-garcia/vaultwarden/discussions" target="_blank" rel="noreferrer">Github Discussions</a>
|
| <a href="https://github.com/dani-garcia/vaultwarden/discussions" target="_blank" rel="noreferrer noopener">Github Discussions</a>
|
||||||
</dd>
|
</dd>
|
||||||
</dl>
|
</dl>
|
||||||
<dl class="row">
|
<dl class="row">
|
||||||
|
@@ -49,7 +49,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.6.0.slim.js"></script>
|
<script src="{{urlpath}}/vw_static/jquery-3.6.1.slim.js"></script>
|
||||||
<script src="{{urlpath}}/vw_static/datatables.js"></script>
|
<script src="{{urlpath}}/vw_static/datatables.js"></script>
|
||||||
<script>
|
<script>
|
||||||
'use strict';
|
'use strict';
|
||||||
|
@@ -122,7 +122,7 @@
|
|||||||
This does not include any configuration or file attachment data that may
|
This does not include any configuration or file attachment data that may
|
||||||
also be needed to fully restore a vaultwarden instance. For details on
|
also be needed to fully restore a vaultwarden instance. For details on
|
||||||
how to perform complete backups, refer to the wiki page on
|
how to perform complete backups, refer to the wiki page on
|
||||||
<a href="https://github.com/dani-garcia/vaultwarden/wiki/Backing-up-your-vault">backups</a>.
|
<a href="https://github.com/dani-garcia/vaultwarden/wiki/Backing-up-your-vault" target="_blank" rel="noopener noreferrer">backups</a>.
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="btn btn-primary" onclick="backupDatabase();">Backup Database</button>
|
<button type="button" class="btn btn-primary" onclick="backupDatabase();">Backup Database</button>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -136,7 +136,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.6.0.slim.js"></script>
|
<script src="{{urlpath}}/vw_static/jquery-3.6.1.slim.js"></script>
|
||||||
<script src="{{urlpath}}/vw_static/datatables.js"></script>
|
<script src="{{urlpath}}/vw_static/datatables.js"></script>
|
||||||
<script>
|
<script>
|
||||||
'use strict';
|
'use strict';
|
||||||
|
95
src/util.rs
95
src/util.rs
@@ -1,7 +1,7 @@
|
|||||||
//
|
//
|
||||||
// Web Headers and caching
|
// Web Headers and caching
|
||||||
//
|
//
|
||||||
use std::io::Cursor;
|
use std::io::{Cursor, ErrorKind};
|
||||||
|
|
||||||
use rocket::{
|
use rocket::{
|
||||||
fairing::{Fairing, Info, Kind},
|
fairing::{Fairing, Info, Kind},
|
||||||
@@ -60,19 +60,34 @@ impl Fairing for AppHeaders {
|
|||||||
// Leaked Passwords check: api.pwnedpasswords.com
|
// Leaked Passwords check: api.pwnedpasswords.com
|
||||||
// 2FA/MFA Site check: 2fa.directory
|
// 2FA/MFA Site check: 2fa.directory
|
||||||
// # Mail Relay: https://bitwarden.com/blog/add-privacy-and-security-using-email-aliases-with-bitwarden/
|
// # Mail Relay: https://bitwarden.com/blog/add-privacy-and-security-using-email-aliases-with-bitwarden/
|
||||||
// app.simplelogin.io, app.anonaddy.com, relay.firefox.com
|
// app.simplelogin.io, app.anonaddy.com, api.fastmail.com, quack.duckduckgo.com
|
||||||
let csp = format!(
|
let csp = format!(
|
||||||
"default-src 'self'; \
|
"default-src 'self'; \
|
||||||
|
object-src 'self' blob:; \
|
||||||
script-src 'self'{script_src}; \
|
script-src 'self'{script_src}; \
|
||||||
style-src 'self' 'unsafe-inline'; \
|
style-src 'self' 'unsafe-inline'; \
|
||||||
img-src 'self' data: https://haveibeenpwned.com/ https://www.gravatar.com {icon_service_csp}; \
|
|
||||||
child-src 'self' https://*.duosecurity.com https://*.duofederal.com; \
|
child-src 'self' https://*.duosecurity.com https://*.duofederal.com; \
|
||||||
frame-src 'self' https://*.duosecurity.com https://*.duofederal.com; \
|
frame-src 'self' https://*.duosecurity.com https://*.duofederal.com; \
|
||||||
connect-src 'self' https://api.pwnedpasswords.com/range/ https://2fa.directory/api/ https://app.simplelogin.io/api/ https://app.anonaddy.com/api/ https://relay.firefox.com/api/; \
|
frame-ancestors 'self' \
|
||||||
object-src 'self' blob:; \
|
chrome-extension://nngceckbapebfimnlniiiahkandclblb \
|
||||||
frame-ancestors 'self' chrome-extension://nngceckbapebfimnlniiiahkandclblb chrome-extension://jbkfoedolllekgbhcbcoahefnbanhhlh moz-extension://* {allowed_iframe_ancestors};",
|
chrome-extension://jbkfoedolllekgbhcbcoahefnbanhhlh \
|
||||||
icon_service_csp=CONFIG._icon_service_csp(),
|
moz-extension://* \
|
||||||
allowed_iframe_ancestors=CONFIG.allowed_iframe_ancestors()
|
{allowed_iframe_ancestors}; \
|
||||||
|
img-src 'self' data: \
|
||||||
|
https://haveibeenpwned.com/ \
|
||||||
|
https://www.gravatar.com \
|
||||||
|
{icon_service_csp}; \
|
||||||
|
connect-src 'self' \
|
||||||
|
https://api.pwnedpasswords.com/range/ \
|
||||||
|
https://2fa.directory/api/ \
|
||||||
|
https://app.simplelogin.io/api/ \
|
||||||
|
https://app.anonaddy.com/api/ \
|
||||||
|
https://api.fastmail.com/ \
|
||||||
|
https://quack.duckduckgo.com/api/email/ \
|
||||||
|
;\
|
||||||
|
",
|
||||||
|
icon_service_csp = CONFIG._icon_service_csp(),
|
||||||
|
allowed_iframe_ancestors = CONFIG.allowed_iframe_ancestors()
|
||||||
);
|
);
|
||||||
res.set_raw_header("Content-Security-Policy", csp);
|
res.set_raw_header("Content-Security-Policy", csp);
|
||||||
res.set_raw_header("X-Frame-Options", "SAMEORIGIN");
|
res.set_raw_header("X-Frame-Options", "SAMEORIGIN");
|
||||||
@@ -311,7 +326,16 @@ pub fn file_exists(path: &str) -> bool {
|
|||||||
|
|
||||||
pub fn write_file(path: &str, content: &[u8]) -> Result<(), crate::error::Error> {
|
pub fn write_file(path: &str, content: &[u8]) -> Result<(), crate::error::Error> {
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
let mut f = File::create(path)?;
|
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.write_all(content)?;
|
||||||
f.flush()?;
|
f.flush()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -357,6 +381,7 @@ pub fn get_uuid() -> String {
|
|||||||
|
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub fn upcase_first(s: &str) -> String {
|
pub fn upcase_first(s: &str) -> String {
|
||||||
let mut c = s.chars();
|
let mut c = s.chars();
|
||||||
match c.next() {
|
match c.next() {
|
||||||
@@ -365,6 +390,15 @@ pub fn upcase_first(s: &str) -> String {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn lcase_first(s: &str) -> String {
|
||||||
|
let mut c = s.chars();
|
||||||
|
match c.next() {
|
||||||
|
None => String::new(),
|
||||||
|
Some(f) => f.to_lowercase().collect::<String>() + c.as_str(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn try_parse_string<S, T>(string: Option<S>) -> Option<T>
|
pub fn try_parse_string<S, T>(string: Option<S>) -> Option<T>
|
||||||
where
|
where
|
||||||
S: AsRef<str>,
|
S: AsRef<str>,
|
||||||
@@ -650,3 +684,46 @@ pub fn get_reqwest_client_builder() -> ClientBuilder {
|
|||||||
headers.insert(header::USER_AGENT, header::HeaderValue::from_static("Vaultwarden"));
|
headers.insert(header::USER_AGENT, header::HeaderValue::from_static("Vaultwarden"));
|
||||||
Client::builder().default_headers(headers).timeout(Duration::from_secs(10))
|
Client::builder().default_headers(headers).timeout(Duration::from_secs(10))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn convert_json_key_lcase_first(src_json: Value) -> Value {
|
||||||
|
match src_json {
|
||||||
|
Value::Array(elm) => {
|
||||||
|
let mut new_array: Vec<Value> = Vec::with_capacity(elm.len());
|
||||||
|
|
||||||
|
for obj in elm {
|
||||||
|
new_array.push(convert_json_key_lcase_first(obj));
|
||||||
|
}
|
||||||
|
Value::Array(new_array)
|
||||||
|
}
|
||||||
|
|
||||||
|
Value::Object(obj) => {
|
||||||
|
let mut json_map = JsonMap::new();
|
||||||
|
for (key, value) in obj.iter() {
|
||||||
|
match (key, value) {
|
||||||
|
(key, Value::Object(elm)) => {
|
||||||
|
let inner_value = convert_json_key_lcase_first(Value::Object(elm.clone()));
|
||||||
|
json_map.insert(lcase_first(key), inner_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
(key, Value::Array(elm)) => {
|
||||||
|
let mut inner_array: Vec<Value> = Vec::with_capacity(elm.len());
|
||||||
|
|
||||||
|
for inner_obj in elm {
|
||||||
|
inner_array.push(convert_json_key_lcase_first(inner_obj.clone()));
|
||||||
|
}
|
||||||
|
|
||||||
|
json_map.insert(lcase_first(key), Value::Array(inner_array));
|
||||||
|
}
|
||||||
|
|
||||||
|
(key, value) => {
|
||||||
|
json_map.insert(lcase_first(key), value.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Value::Object(json_map)
|
||||||
|
}
|
||||||
|
|
||||||
|
value => value,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user