mirror of
https://github.com/dani-garcia/vaultwarden.git
synced 2025-09-10 18:55:57 +03:00
Compare commits
76 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
1c7338c7c4 | ||
|
08f37b9935 | ||
|
4826ddca4c | ||
|
2b32b6f78c | ||
|
a6cfdddfd8 | ||
|
814ce9a6ac | ||
|
1bee46f64b | ||
|
556d945396 | ||
|
664b480c71 | ||
|
84e901b7d2 | ||
|
839b2bc950 | ||
|
6050c8dac5 | ||
|
0a6b797e6e | ||
|
fb6f441a4f | ||
|
9876aedd67 | ||
|
19e671ff25 | ||
|
60964c07e6 | ||
|
e4894524e4 | ||
|
e7f083dee9 | ||
|
1074315a87 | ||
|
c56bf38079 | ||
|
3c0cac623d | ||
|
550794b127 | ||
|
e818a0bf37 | ||
|
2aedff50e8 | ||
|
84a23008f4 | ||
|
44e9e1a58e | ||
|
e4606431d1 | ||
|
5b7d7390b0 | ||
|
a05187c0ff | ||
|
8e34495e73 | ||
|
4219249e11 | ||
|
bd883de70e | ||
|
2d66292350 | ||
|
adf67a8ee8 | ||
|
f40f5b8399 | ||
|
2d6ca0ea95 | ||
|
06a10e2c5a | ||
|
445680fb84 | ||
|
83376544d8 | ||
|
04a17dcdef | ||
|
0851561392 | ||
|
95cd6deda6 | ||
|
636f16dc66 | ||
|
9e5b049dca | ||
|
23aa9088f3 | ||
|
4f0ed06b06 | ||
|
349c97efaf | ||
|
8b05a5d192 | ||
|
83bf77d713 | ||
|
4d5c047ddc | ||
|
147c9c7b50 | ||
|
6515a2fcad | ||
|
4a2ed553df | ||
|
ba492c0602 | ||
|
1ec049e2b5 | ||
|
0fb8563b13 | ||
|
f906f6230a | ||
|
951ba55123 | ||
|
18abf226be | ||
|
393645617e | ||
|
5bf243b675 | ||
|
cfba8347a3 | ||
|
55c1b6e8d5 | ||
|
3d7e80a7aa | ||
|
5866338de4 | ||
|
271e3ae757 | ||
|
48cc31a59f | ||
|
6a7cee4e7e | ||
|
f850dbb310 | ||
|
07099df41a | ||
|
0c0a80720e | ||
|
ae437f70a3 | ||
|
3d11f4cd16 | ||
|
3bd4e42fb0 | ||
|
89e94b1d91 |
@@ -72,6 +72,13 @@
|
|||||||
# WEBSOCKET_ADDRESS=0.0.0.0
|
# WEBSOCKET_ADDRESS=0.0.0.0
|
||||||
# WEBSOCKET_PORT=3012
|
# WEBSOCKET_PORT=3012
|
||||||
|
|
||||||
|
## Enables push notifications (requires key and id from https://bitwarden.com/host)
|
||||||
|
# PUSH_ENABLED=true
|
||||||
|
# PUSH_INSTALLATION_ID=CHANGEME
|
||||||
|
# PUSH_INSTALLATION_KEY=CHANGEME
|
||||||
|
## Don't change this unless you know what you're doing.
|
||||||
|
# PUSH_RELAY_BASE_URI=https://push.bitwarden.com
|
||||||
|
|
||||||
## Controls whether users are allowed to create Bitwarden Sends.
|
## Controls whether users are allowed to create Bitwarden Sends.
|
||||||
## This setting applies globally to all users.
|
## This setting applies globally to all users.
|
||||||
## To control this on a per-org basis instead, use the "Disable Send" org policy.
|
## To control this on a per-org basis instead, use the "Disable Send" org policy.
|
||||||
@@ -264,6 +271,8 @@
|
|||||||
## For details see: https://github.com/dani-garcia/vaultwarden/wiki/Enabling-admin-page#secure-the-admin_token
|
## For details see: https://github.com/dani-garcia/vaultwarden/wiki/Enabling-admin-page#secure-the-admin_token
|
||||||
## If not set, the admin panel is disabled
|
## If not set, the admin panel is disabled
|
||||||
## New Argon2 PHC string
|
## New Argon2 PHC string
|
||||||
|
## Note that for some environments, like docker-compose you need to escape all the dollar signs `$` with an extra dollar sign like `$$`
|
||||||
|
## Also, use single quotes (') instead of double quotes (") to enclose the string when needed
|
||||||
# ADMIN_TOKEN='$argon2id$v=19$m=65540,t=3,p=4$MmeKRnGK5RW5mJS7h3TOL89GrpLPXJPAtTK8FTqj9HM$DqsstvoSAETl9YhnsXbf43WeaUwJC6JhViIvuPoig78'
|
# ADMIN_TOKEN='$argon2id$v=19$m=65540,t=3,p=4$MmeKRnGK5RW5mJS7h3TOL89GrpLPXJPAtTK8FTqj9HM$DqsstvoSAETl9YhnsXbf43WeaUwJC6JhViIvuPoig78'
|
||||||
## Old plain text string (Will generate warnings in favor of Argon2)
|
## Old plain text string (Will generate warnings in favor of Argon2)
|
||||||
# ADMIN_TOKEN=Vy2VyYTTsKPv8W5aEOWUbB/Bt3DEKePbHmI4m9VcemUMS2rEviDowNAFqYi1xjmp
|
# ADMIN_TOKEN=Vy2VyYTTsKPv8W5aEOWUbB/Bt3DEKePbHmI4m9VcemUMS2rEviDowNAFqYi1xjmp
|
||||||
|
10
.github/workflows/build.yml
vendored
10
.github/workflows/build.yml
vendored
@@ -30,7 +30,7 @@ jobs:
|
|||||||
# This is done globally to prevent rebuilds when the RUSTFLAGS env variable changes.
|
# This is done globally to prevent rebuilds when the RUSTFLAGS env variable changes.
|
||||||
env:
|
env:
|
||||||
RUSTFLAGS: "-D warnings"
|
RUSTFLAGS: "-D warnings"
|
||||||
CARGO_REGISTRIES_CRATES_IO_PROTOCOL: git # Use the old git protocol until it is stable probably in 1.68 or 1.69. MSRV needs to be at this before removed.
|
CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
@@ -43,7 +43,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
# Checkout the repo
|
# Checkout the repo
|
||||||
- name: "Checkout"
|
- name: "Checkout"
|
||||||
uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3.5.0
|
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
|
||||||
# End Checkout the repo
|
# End Checkout the repo
|
||||||
|
|
||||||
|
|
||||||
@@ -71,7 +71,7 @@ jobs:
|
|||||||
|
|
||||||
# Only install the clippy and rustfmt components on the default rust-toolchain
|
# Only install the clippy and rustfmt components on the default rust-toolchain
|
||||||
- name: "Install rust-toolchain version"
|
- name: "Install rust-toolchain version"
|
||||||
uses: dtolnay/rust-toolchain@fc3253060d0c959bea12a59f10f8391454a0b02d # master @ 2023-03-21 - 06:36 GMT+1
|
uses: dtolnay/rust-toolchain@b44cb146d03e8d870c57ab64b80f04586349ca5d # master @ 2023-03-28 - 06:32 GMT+2
|
||||||
if: ${{ matrix.channel == 'rust-toolchain' }}
|
if: ${{ matrix.channel == 'rust-toolchain' }}
|
||||||
with:
|
with:
|
||||||
toolchain: "${{steps.toolchain.outputs.RUST_TOOLCHAIN}}"
|
toolchain: "${{steps.toolchain.outputs.RUST_TOOLCHAIN}}"
|
||||||
@@ -81,7 +81,7 @@ jobs:
|
|||||||
|
|
||||||
# Install the any other channel to be used for which we do not execute clippy and rustfmt
|
# Install the any other channel to be used for which we do not execute clippy and rustfmt
|
||||||
- name: "Install MSRV version"
|
- name: "Install MSRV version"
|
||||||
uses: dtolnay/rust-toolchain@fc3253060d0c959bea12a59f10f8391454a0b02d # master @ 2023-03-21 - 06:36 GMT+1
|
uses: dtolnay/rust-toolchain@b44cb146d03e8d870c57ab64b80f04586349ca5d # master @ 2023-03-28 - 06:32 GMT+2
|
||||||
if: ${{ matrix.channel != 'rust-toolchain' }}
|
if: ${{ matrix.channel != 'rust-toolchain' }}
|
||||||
with:
|
with:
|
||||||
toolchain: "${{steps.toolchain.outputs.RUST_TOOLCHAIN}}"
|
toolchain: "${{steps.toolchain.outputs.RUST_TOOLCHAIN}}"
|
||||||
@@ -89,7 +89,7 @@ jobs:
|
|||||||
|
|
||||||
|
|
||||||
# Enable Rust Caching
|
# Enable Rust Caching
|
||||||
- uses: Swatinem/rust-cache@6fd3edff6979b79f87531400ad694fb7f2c84b1f # v2.2.1
|
- uses: Swatinem/rust-cache@2656b87321093db1cb55fbd73183d195214fdfd1 # v2.5.0
|
||||||
# End Enable Rust Caching
|
# End Enable Rust Caching
|
||||||
|
|
||||||
|
|
||||||
|
2
.github/workflows/hadolint.yml
vendored
2
.github/workflows/hadolint.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
# Checkout the repo
|
# Checkout the repo
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3.5.0
|
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
|
||||||
# End Checkout the repo
|
# End Checkout the repo
|
||||||
|
|
||||||
|
|
||||||
|
8
.github/workflows/release.yml
vendored
8
.github/workflows/release.yml
vendored
@@ -73,7 +73,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
# Checkout the repo
|
# Checkout the repo
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3.5.0
|
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
@@ -92,7 +92,7 @@ jobs:
|
|||||||
|
|
||||||
# Login to Docker Hub
|
# Login to Docker Hub
|
||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a # v2.1.0
|
uses: docker/login-action@465a07811f14bebb1938fbed4728c6a1ff8901fc # v2.2.0
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
@@ -100,7 +100,7 @@ jobs:
|
|||||||
|
|
||||||
# Login to GitHub Container Registry
|
# Login to GitHub Container Registry
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a # v2.1.0
|
uses: docker/login-action@465a07811f14bebb1938fbed4728c6a1ff8901fc # v2.2.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
@@ -109,7 +109,7 @@ jobs:
|
|||||||
|
|
||||||
# Login to Quay.io
|
# Login to Quay.io
|
||||||
- name: Login to Quay.io
|
- name: Login to Quay.io
|
||||||
uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a # v2.1.0
|
uses: docker/login-action@465a07811f14bebb1938fbed4728c6a1ff8901fc # v2.2.0
|
||||||
with:
|
with:
|
||||||
registry: quay.io
|
registry: quay.io
|
||||||
username: ${{ secrets.QUAY_USERNAME }}
|
username: ${{ secrets.QUAY_USERNAME }}
|
||||||
|
1147
Cargo.lock
generated
1147
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
69
Cargo.toml
69
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.66.1"
|
rust-version = "1.68.2"
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
|
|
||||||
repository = "https://github.com/dani-garcia/vaultwarden"
|
repository = "https://github.com/dani-garcia/vaultwarden"
|
||||||
@@ -36,11 +36,11 @@ unstable = []
|
|||||||
|
|
||||||
[target."cfg(not(windows))".dependencies]
|
[target."cfg(not(windows))".dependencies]
|
||||||
# Logging
|
# Logging
|
||||||
syslog = "6.0.1" # Needs to be v4 until fern is updated
|
syslog = "6.1.0"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# Logging
|
# Logging
|
||||||
log = "0.4.17"
|
log = "0.4.19"
|
||||||
fern = { version = "0.6.2", features = ["syslog-6"] }
|
fern = { version = "0.6.2", features = ["syslog-6"] }
|
||||||
tracing = { version = "0.1.37", 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
|
||||||
|
|
||||||
@@ -48,17 +48,19 @@ tracing = { version = "0.1.37", features = ["log"] } # Needed to have lettre and
|
|||||||
dotenvy = { version = "0.15.7", default-features = false }
|
dotenvy = { version = "0.15.7", default-features = false }
|
||||||
|
|
||||||
# Lazy initialization
|
# Lazy initialization
|
||||||
once_cell = "1.17.1"
|
once_cell = "1.18.0"
|
||||||
|
|
||||||
# Numerical libraries
|
# Numerical libraries
|
||||||
num-traits = "0.2.15"
|
num-traits = "0.2.15"
|
||||||
num-derive = "0.3.3"
|
num-derive = "0.4.0"
|
||||||
|
|
||||||
# Web framework
|
# Web framework
|
||||||
rocket = { version = "0.5.0-rc.3", features = ["tls", "json"], default-features = false }
|
rocket = { version = "0.5.0-rc.3", features = ["tls", "json"], default-features = false }
|
||||||
|
# rocket_ws = { version ="0.1.0-rc.3" }
|
||||||
|
rocket_ws = { git = 'https://github.com/SergioBenitez/Rocket', rev = "ce441b5f46fdf5cd99cb32b8b8638835e4c2a5fa" } # v0.5 branch
|
||||||
|
|
||||||
# WebSockets libraries
|
# WebSockets libraries
|
||||||
tokio-tungstenite = "0.18.0"
|
tokio-tungstenite = "0.19.0"
|
||||||
rmpv = "1.0.0" # MessagePack library
|
rmpv = "1.0.0" # MessagePack library
|
||||||
|
|
||||||
# Concurrent HashMap used for WebSocket messaging and favicons
|
# Concurrent HashMap used for WebSocket messaging and favicons
|
||||||
@@ -66,37 +68,37 @@ dashmap = "5.4.0"
|
|||||||
|
|
||||||
# Async futures
|
# Async futures
|
||||||
futures = "0.3.28"
|
futures = "0.3.28"
|
||||||
tokio = { version = "1.27.0", features = ["rt-multi-thread", "fs", "io-util", "parking_lot", "time", "signal"] }
|
tokio = { version = "1.29.1", features = ["rt-multi-thread", "fs", "io-util", "parking_lot", "time", "signal"] }
|
||||||
|
|
||||||
# A generic serialization/deserialization framework
|
# A generic serialization/deserialization framework
|
||||||
serde = { version = "1.0.159", features = ["derive"] }
|
serde = { version = "1.0.166", features = ["derive"] }
|
||||||
serde_json = "1.0.95"
|
serde_json = "1.0.99"
|
||||||
|
|
||||||
# A safe, extensible ORM and Query builder
|
# A safe, extensible ORM and Query builder
|
||||||
diesel = { version = "2.0.3", features = ["chrono", "r2d2"] }
|
diesel = { version = "2.1.0", features = ["chrono", "r2d2"] }
|
||||||
diesel_migrations = "2.0.0"
|
diesel_migrations = "2.1.0"
|
||||||
diesel_logger = { version = "0.2.0", optional = true }
|
diesel_logger = { version = "0.3.0", optional = true }
|
||||||
|
|
||||||
# Bundled/Static SQLite
|
# Bundled/Static SQLite
|
||||||
libsqlite3-sys = { version = "0.25.2", features = ["bundled"], optional = true }
|
libsqlite3-sys = { version = "0.26.0", features = ["bundled"], optional = true }
|
||||||
|
|
||||||
# Crypto-related libraries
|
# Crypto-related libraries
|
||||||
rand = { version = "0.8.5", features = ["small_rng"] }
|
rand = { version = "0.8.5", features = ["small_rng"] }
|
||||||
ring = "0.16.20"
|
ring = "0.16.20"
|
||||||
|
|
||||||
# UUID generation
|
# UUID generation
|
||||||
uuid = { version = "1.3.0", features = ["v4"] }
|
uuid = { version = "1.4.0", features = ["v4"] }
|
||||||
|
|
||||||
# Date and time libraries
|
# Date and time libraries
|
||||||
chrono = { version = "0.4.24", features = ["clock", "serde"], default-features = false }
|
chrono = { version = "0.4.26", features = ["clock", "serde"], default-features = false }
|
||||||
chrono-tz = "0.8.1"
|
chrono-tz = "0.8.3"
|
||||||
time = "0.3.20"
|
time = "0.3.22"
|
||||||
|
|
||||||
# Job scheduler
|
# Job scheduler
|
||||||
job_scheduler_ng = "2.0.4"
|
job_scheduler_ng = "2.0.4"
|
||||||
|
|
||||||
# Data encoding library Hex/Base32/Base64
|
# Data encoding library Hex/Base32/Base64
|
||||||
data-encoding = "2.3.3"
|
data-encoding = "2.4.0"
|
||||||
|
|
||||||
# JWT library
|
# JWT library
|
||||||
jsonwebtoken = "8.3.0"
|
jsonwebtoken = "8.3.0"
|
||||||
@@ -111,40 +113,40 @@ 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 and favicons
|
# Handling of URL's for WebAuthn and favicons
|
||||||
url = "2.3.1"
|
url = "2.4.0"
|
||||||
|
|
||||||
# Email libraries
|
# Email libraries
|
||||||
lettre = { version = "0.10.3", features = ["smtp-transport", "sendmail-transport", "builder", "serde", "tokio1-native-tls", "hostname", "tracing", "tokio1"], default-features = false }
|
lettre = { version = "0.10.4", features = ["smtp-transport", "sendmail-transport", "builder", "serde", "tokio1-native-tls", "hostname", "tracing", "tokio1"], default-features = false }
|
||||||
percent-encoding = "2.2.0" # URL encoding library used for URL's in the emails
|
percent-encoding = "2.3.0" # URL encoding library used for URL's in the emails
|
||||||
email_address = "0.2.4"
|
email_address = "0.2.4"
|
||||||
|
|
||||||
# HTML Template library
|
# HTML Template library
|
||||||
handlebars = { version = "4.3.6", features = ["dir_source"] }
|
handlebars = { version = "4.3.7", features = ["dir_source"] }
|
||||||
|
|
||||||
# HTTP client (Used for favicons, version check, DUO and HIBP API)
|
# HTTP client (Used for favicons, version check, DUO and HIBP API)
|
||||||
reqwest = { version = "0.11.16", features = ["stream", "json", "gzip", "brotli", "socks", "cookies", "trust-dns"] }
|
reqwest = { version = "0.11.18", features = ["stream", "json", "gzip", "brotli", "socks", "cookies", "trust-dns"] }
|
||||||
|
|
||||||
# Favicon extraction libraries
|
# Favicon extraction libraries
|
||||||
html5gum = "0.5.2"
|
html5gum = "0.5.3"
|
||||||
regex = { version = "1.7.3", features = ["std", "perf", "unicode-perl"], default-features = false }
|
regex = { version = "1.8.4", features = ["std", "perf", "unicode-perl"], default-features = false }
|
||||||
data-url = "0.2.0"
|
data-url = "0.3.0"
|
||||||
bytes = "1.4.0"
|
bytes = "1.4.0"
|
||||||
|
|
||||||
# Cache function results (Used for version check and favicon fetching)
|
# Cache function results (Used for version check and favicon fetching)
|
||||||
cached = "0.42.0"
|
cached = "0.44.0"
|
||||||
|
|
||||||
# Used for custom short lived cookie jar during favicon extraction
|
# Used for custom short lived cookie jar during favicon extraction
|
||||||
cookie = "0.16.2"
|
cookie = "0.16.2"
|
||||||
cookie_store = "0.19.0"
|
cookie_store = "0.19.1"
|
||||||
|
|
||||||
# Used by U2F, JWT and PostgreSQL
|
# Used by U2F, JWT and PostgreSQL
|
||||||
openssl = "0.10.48"
|
openssl = "0.10.55"
|
||||||
|
|
||||||
# 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.12"
|
paste = "1.0.13"
|
||||||
governor = "0.5.1"
|
governor = "0.5.1"
|
||||||
|
|
||||||
# Check client versions for specific features.
|
# Check client versions for specific features.
|
||||||
@@ -152,8 +154,7 @@ semver = "1.0.17"
|
|||||||
|
|
||||||
# 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.34", features = ["secure"], default-features = false, optional = true }
|
mimalloc = { version = "0.1.37", features = ["secure"], default-features = false, optional = true }
|
||||||
libmimalloc-sys = "=0.1.30"
|
|
||||||
which = "4.4.0"
|
which = "4.4.0"
|
||||||
|
|
||||||
# Argon2 library with support for the PHC format
|
# Argon2 library with support for the PHC format
|
||||||
@@ -162,6 +163,10 @@ argon2 = "0.5.0"
|
|||||||
# Reading a password from the cli for generating the Argon2id ADMIN_TOKEN
|
# Reading a password from the cli for generating the Argon2id ADMIN_TOKEN
|
||||||
rpassword = "7.2.0"
|
rpassword = "7.2.0"
|
||||||
|
|
||||||
|
[patch.crates-io]
|
||||||
|
rocket = { git = 'https://github.com/SergioBenitez/Rocket', rev = 'ce441b5f46fdf5cd99cb32b8b8638835e4c2a5fa' } # v0.5 branch
|
||||||
|
# rocket_ws = { git = 'https://github.com/SergioBenitez/Rocket', rev = 'ce441b5f46fdf5cd99cb32b8b8638835e4c2a5fa' } # v0.5 branch
|
||||||
|
|
||||||
# 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
|
||||||
[profile.release]
|
[profile.release]
|
||||||
|
@@ -38,7 +38,7 @@ Pull the docker image and mount a volume from the host for persistent storage:
|
|||||||
|
|
||||||
```sh
|
```sh
|
||||||
docker pull vaultwarden/server:latest
|
docker pull vaultwarden/server:latest
|
||||||
docker run -d --name vaultwarden -v /vw-data/:/data/ -p 80:80 vaultwarden/server:latest
|
docker run -d --name vaultwarden -v /vw-data/:/data/ --restart unless-stopped -p 80:80 vaultwarden/server:latest
|
||||||
```
|
```
|
||||||
This will preserve any persistent data under /vw-data/, you can adapt the path to whatever suits you.
|
This will preserve any persistent data under /vw-data/, you can adapt the path to whatever suits you.
|
||||||
|
|
||||||
|
2
build.rs
2
build.rs
@@ -72,7 +72,7 @@ fn version_from_git_info() -> Result<String, std::io::Error> {
|
|||||||
// Combined version
|
// Combined version
|
||||||
if let Some(exact) = exact_tag {
|
if let Some(exact) = exact_tag {
|
||||||
Ok(exact)
|
Ok(exact)
|
||||||
} else if &branch != "main" && &branch != "master" {
|
} else if &branch != "main" && &branch != "master" && &branch != "HEAD" {
|
||||||
Ok(format!("{last_tag}-{rev_short} ({branch})"))
|
Ok(format!("{last_tag}-{rev_short} ({branch})"))
|
||||||
} else {
|
} else {
|
||||||
Ok(format!("{last_tag}-{rev_short}"))
|
Ok(format!("{last_tag}-{rev_short}"))
|
||||||
|
@@ -2,42 +2,42 @@
|
|||||||
|
|
||||||
# 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 rust_version = "1.68.2" %}
|
{% set rust_version = "1.70.0" %}
|
||||||
{% set debian_version = "bullseye" %}
|
{% set debian_version = "bullseye" %}
|
||||||
{% set alpine_version = "3.17" %}
|
{% set alpine_version = "3.17" %}
|
||||||
{% set build_stage_base_image = "rust:%s-%s" % (rust_version, debian_version) %}
|
{% set build_stage_base_image = "docker.io/library/rust:%s-%s" % (rust_version, debian_version) %}
|
||||||
{% 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-%s" % rust_version %}
|
{% set build_stage_base_image = "docker.io/blackdex/rust-musl:x86_64-musl-stable-%s" % rust_version %}
|
||||||
{% set runtime_stage_base_image = "alpine:%s" % alpine_version %}
|
{% set runtime_stage_base_image = "docker.io/library/alpine:%s" % alpine_version %}
|
||||||
{% 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-%s" % rust_version %}
|
{% set build_stage_base_image = "docker.io/blackdex/rust-musl:armv7-musleabihf-stable-%s" % rust_version %}
|
||||||
{% set runtime_stage_base_image = "balenalib/armv7hf-alpine:%s" % alpine_version %}
|
{% set runtime_stage_base_image = "docker.io/balenalib/armv7hf-alpine:%s" % alpine_version %}
|
||||||
{% 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-%s" % rust_version %}
|
{% set build_stage_base_image = "docker.io/blackdex/rust-musl:arm-musleabi-stable-%s" % rust_version %}
|
||||||
{% set runtime_stage_base_image = "balenalib/rpi-alpine:%s" % alpine_version %}
|
{% set runtime_stage_base_image = "docker.io/balenalib/rpi-alpine:%s" % alpine_version %}
|
||||||
{% 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-%s" % rust_version %}
|
{% set build_stage_base_image = "docker.io/blackdex/rust-musl:aarch64-musl-stable-%s" % rust_version %}
|
||||||
{% set runtime_stage_base_image = "balenalib/aarch64-alpine:%s" % alpine_version %}
|
{% set runtime_stage_base_image = "docker.io/balenalib/aarch64-alpine:%s" % alpine_version %}
|
||||||
{% 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 %}
|
||||||
{% set runtime_stage_base_image = "debian:%s-slim" % debian_version %}
|
{% set runtime_stage_base_image = "docker.io/library/debian:%s-slim" % debian_version %}
|
||||||
{% elif "arm64" in target_file %}
|
{% elif "arm64" in target_file %}
|
||||||
{% set runtime_stage_base_image = "balenalib/aarch64-debian:%s" % debian_version %}
|
{% set runtime_stage_base_image = "docker.io/balenalib/aarch64-debian:%s" % debian_version %}
|
||||||
{% set package_arch_name = "arm64" %}
|
{% set package_arch_name = "arm64" %}
|
||||||
{% set package_arch_target = "aarch64-unknown-linux-gnu" %}
|
{% set package_arch_target = "aarch64-unknown-linux-gnu" %}
|
||||||
{% set package_cross_compiler = "aarch64-linux-gnu" %}
|
{% set package_cross_compiler = "aarch64-linux-gnu" %}
|
||||||
{% elif "armv6" in target_file %}
|
{% elif "armv6" in target_file %}
|
||||||
{% set runtime_stage_base_image = "balenalib/rpi-debian:%s" % debian_version %}
|
{% set runtime_stage_base_image = "docker.io/balenalib/rpi-debian:%s" % debian_version %}
|
||||||
{% set package_arch_name = "armel" %}
|
{% set package_arch_name = "armel" %}
|
||||||
{% set package_arch_target = "arm-unknown-linux-gnueabi" %}
|
{% set package_arch_target = "arm-unknown-linux-gnueabi" %}
|
||||||
{% set package_cross_compiler = "arm-linux-gnueabi" %}
|
{% set package_cross_compiler = "arm-linux-gnueabi" %}
|
||||||
{% elif "armv7" in target_file %}
|
{% elif "armv7" in target_file %}
|
||||||
{% set runtime_stage_base_image = "balenalib/armv7hf-debian:%s" % debian_version %}
|
{% set runtime_stage_base_image = "docker.io/balenalib/armv7hf-debian:%s" % debian_version %}
|
||||||
{% set package_arch_name = "armhf" %}
|
{% set package_arch_name = "armhf" %}
|
||||||
{% set package_arch_target = "armv7-unknown-linux-gnueabihf" %}
|
{% set package_arch_target = "armv7-unknown-linux-gnueabihf" %}
|
||||||
{% set package_cross_compiler = "arm-linux-gnueabihf" %}
|
{% set package_cross_compiler = "arm-linux-gnueabihf" %}
|
||||||
@@ -61,8 +61,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 = "v2023.3.0b" %}
|
{% set vault_version = "v2023.5.0" %}
|
||||||
{% set vault_image_digest = "sha256:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee" %}
|
{% set vault_image_digest = "sha256:e5b5e99d132d50dc73176afb65f41cf3b834fb06bfa1d621ac16c705c3f10085" %}
|
||||||
# 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
|
||||||
@@ -72,15 +72,15 @@
|
|||||||
# - 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:{{ vault_version }}
|
# $ docker pull docker.io/vaultwarden/web-vault:{{ vault_version }}
|
||||||
# $ docker image inspect --format "{{ '{{' }}.RepoDigests}}" vaultwarden/web-vault:{{ vault_version }}
|
# $ docker image inspect --format "{{ '{{' }}.RepoDigests}}" docker.io/vaultwarden/web-vault:{{ vault_version }}
|
||||||
# [vaultwarden/web-vault@{{ vault_image_digest }}]
|
# [docker.io/vaultwarden/web-vault@{{ vault_image_digest }}]
|
||||||
#
|
#
|
||||||
# - 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@{{ vault_image_digest }}
|
# $ docker image inspect --format "{{ '{{' }}.RepoTags}}" docker.io/vaultwarden/web-vault@{{ vault_image_digest }}
|
||||||
# [vaultwarden/web-vault:{{ vault_version }}]
|
# [docker.io/vaultwarden/web-vault:{{ vault_version }}]
|
||||||
#
|
#
|
||||||
FROM vaultwarden/web-vault@{{ vault_image_digest }} as vault
|
FROM docker.io/vaultwarden/web-vault@{{ vault_image_digest }} as vault
|
||||||
|
|
||||||
########################## BUILD IMAGE ##########################
|
########################## BUILD IMAGE ##########################
|
||||||
FROM {{ build_stage_base_image }} as build
|
FROM {{ build_stage_base_image }} as build
|
||||||
|
@@ -15,18 +15,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:v2023.3.0b
|
# $ docker pull docker.io/vaultwarden/web-vault:v2023.5.0
|
||||||
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2023.3.0b
|
# $ docker image inspect --format "{{.RepoDigests}}" docker.io/vaultwarden/web-vault:v2023.5.0
|
||||||
# [vaultwarden/web-vault@sha256:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee]
|
# [docker.io/vaultwarden/web-vault@sha256:e5b5e99d132d50dc73176afb65f41cf3b834fb06bfa1d621ac16c705c3f10085]
|
||||||
#
|
#
|
||||||
# - 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:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee
|
# $ docker image inspect --format "{{.RepoTags}}" docker.io/vaultwarden/web-vault@sha256:e5b5e99d132d50dc73176afb65f41cf3b834fb06bfa1d621ac16c705c3f10085
|
||||||
# [vaultwarden/web-vault:v2023.3.0b]
|
# [docker.io/vaultwarden/web-vault:v2023.5.0]
|
||||||
#
|
#
|
||||||
FROM vaultwarden/web-vault@sha256:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee as vault
|
FROM docker.io/vaultwarden/web-vault@sha256:e5b5e99d132d50dc73176afb65f41cf3b834fb06bfa1d621ac16c705c3f10085 as vault
|
||||||
|
|
||||||
########################## BUILD IMAGE ##########################
|
########################## BUILD IMAGE ##########################
|
||||||
FROM rust:1.68.2-bullseye as build
|
FROM docker.io/library/rust:1.70.0-bullseye as build
|
||||||
|
|
||||||
# Build time options to avoid dpkg warnings and help with reproducible builds.
|
# Build time options to avoid dpkg warnings and help with reproducible builds.
|
||||||
ENV DEBIAN_FRONTEND=noninteractive \
|
ENV DEBIAN_FRONTEND=noninteractive \
|
||||||
@@ -80,7 +80,7 @@ RUN cargo build --features ${DB} --release
|
|||||||
######################## 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 debian:bullseye-slim
|
FROM docker.io/library/debian:bullseye-slim
|
||||||
|
|
||||||
ENV ROCKET_PROFILE="release" \
|
ENV ROCKET_PROFILE="release" \
|
||||||
ROCKET_ADDRESS=0.0.0.0 \
|
ROCKET_ADDRESS=0.0.0.0 \
|
||||||
|
@@ -15,18 +15,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:v2023.3.0b
|
# $ docker pull docker.io/vaultwarden/web-vault:v2023.5.0
|
||||||
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2023.3.0b
|
# $ docker image inspect --format "{{.RepoDigests}}" docker.io/vaultwarden/web-vault:v2023.5.0
|
||||||
# [vaultwarden/web-vault@sha256:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee]
|
# [docker.io/vaultwarden/web-vault@sha256:e5b5e99d132d50dc73176afb65f41cf3b834fb06bfa1d621ac16c705c3f10085]
|
||||||
#
|
#
|
||||||
# - 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:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee
|
# $ docker image inspect --format "{{.RepoTags}}" docker.io/vaultwarden/web-vault@sha256:e5b5e99d132d50dc73176afb65f41cf3b834fb06bfa1d621ac16c705c3f10085
|
||||||
# [vaultwarden/web-vault:v2023.3.0b]
|
# [docker.io/vaultwarden/web-vault:v2023.5.0]
|
||||||
#
|
#
|
||||||
FROM vaultwarden/web-vault@sha256:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee as vault
|
FROM docker.io/vaultwarden/web-vault@sha256:e5b5e99d132d50dc73176afb65f41cf3b834fb06bfa1d621ac16c705c3f10085 as vault
|
||||||
|
|
||||||
########################## BUILD IMAGE ##########################
|
########################## BUILD IMAGE ##########################
|
||||||
FROM blackdex/rust-musl:x86_64-musl-stable-1.68.2 as build
|
FROM docker.io/blackdex/rust-musl:x86_64-musl-stable-1.70.0 as build
|
||||||
|
|
||||||
# Build time options to avoid dpkg warnings and help with reproducible builds.
|
# Build time options to avoid dpkg warnings and help with reproducible builds.
|
||||||
ENV DEBIAN_FRONTEND=noninteractive \
|
ENV DEBIAN_FRONTEND=noninteractive \
|
||||||
@@ -76,7 +76,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.17
|
FROM docker.io/library/alpine:3.17
|
||||||
|
|
||||||
ENV ROCKET_PROFILE="release" \
|
ENV ROCKET_PROFILE="release" \
|
||||||
ROCKET_ADDRESS=0.0.0.0 \
|
ROCKET_ADDRESS=0.0.0.0 \
|
||||||
|
@@ -15,18 +15,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:v2023.3.0b
|
# $ docker pull docker.io/vaultwarden/web-vault:v2023.5.0
|
||||||
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2023.3.0b
|
# $ docker image inspect --format "{{.RepoDigests}}" docker.io/vaultwarden/web-vault:v2023.5.0
|
||||||
# [vaultwarden/web-vault@sha256:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee]
|
# [docker.io/vaultwarden/web-vault@sha256:e5b5e99d132d50dc73176afb65f41cf3b834fb06bfa1d621ac16c705c3f10085]
|
||||||
#
|
#
|
||||||
# - 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:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee
|
# $ docker image inspect --format "{{.RepoTags}}" docker.io/vaultwarden/web-vault@sha256:e5b5e99d132d50dc73176afb65f41cf3b834fb06bfa1d621ac16c705c3f10085
|
||||||
# [vaultwarden/web-vault:v2023.3.0b]
|
# [docker.io/vaultwarden/web-vault:v2023.5.0]
|
||||||
#
|
#
|
||||||
FROM vaultwarden/web-vault@sha256:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee as vault
|
FROM docker.io/vaultwarden/web-vault@sha256:e5b5e99d132d50dc73176afb65f41cf3b834fb06bfa1d621ac16c705c3f10085 as vault
|
||||||
|
|
||||||
########################## BUILD IMAGE ##########################
|
########################## BUILD IMAGE ##########################
|
||||||
FROM rust:1.68.2-bullseye as build
|
FROM docker.io/library/rust:1.70.0-bullseye as build
|
||||||
|
|
||||||
# Build time options to avoid dpkg warnings and help with reproducible builds.
|
# Build time options to avoid dpkg warnings and help with reproducible builds.
|
||||||
ENV DEBIAN_FRONTEND=noninteractive \
|
ENV DEBIAN_FRONTEND=noninteractive \
|
||||||
@@ -80,7 +80,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 debian:bullseye-slim
|
FROM docker.io/library/debian:bullseye-slim
|
||||||
|
|
||||||
ENV ROCKET_PROFILE="release" \
|
ENV ROCKET_PROFILE="release" \
|
||||||
ROCKET_ADDRESS=0.0.0.0 \
|
ROCKET_ADDRESS=0.0.0.0 \
|
||||||
|
@@ -15,18 +15,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:v2023.3.0b
|
# $ docker pull docker.io/vaultwarden/web-vault:v2023.5.0
|
||||||
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2023.3.0b
|
# $ docker image inspect --format "{{.RepoDigests}}" docker.io/vaultwarden/web-vault:v2023.5.0
|
||||||
# [vaultwarden/web-vault@sha256:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee]
|
# [docker.io/vaultwarden/web-vault@sha256:e5b5e99d132d50dc73176afb65f41cf3b834fb06bfa1d621ac16c705c3f10085]
|
||||||
#
|
#
|
||||||
# - 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:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee
|
# $ docker image inspect --format "{{.RepoTags}}" docker.io/vaultwarden/web-vault@sha256:e5b5e99d132d50dc73176afb65f41cf3b834fb06bfa1d621ac16c705c3f10085
|
||||||
# [vaultwarden/web-vault:v2023.3.0b]
|
# [docker.io/vaultwarden/web-vault:v2023.5.0]
|
||||||
#
|
#
|
||||||
FROM vaultwarden/web-vault@sha256:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee as vault
|
FROM docker.io/vaultwarden/web-vault@sha256:e5b5e99d132d50dc73176afb65f41cf3b834fb06bfa1d621ac16c705c3f10085 as vault
|
||||||
|
|
||||||
########################## BUILD IMAGE ##########################
|
########################## BUILD IMAGE ##########################
|
||||||
FROM blackdex/rust-musl:x86_64-musl-stable-1.68.2 as build
|
FROM docker.io/blackdex/rust-musl:x86_64-musl-stable-1.70.0 as build
|
||||||
|
|
||||||
# Build time options to avoid dpkg warnings and help with reproducible builds.
|
# Build time options to avoid dpkg warnings and help with reproducible builds.
|
||||||
ENV DEBIAN_FRONTEND=noninteractive \
|
ENV DEBIAN_FRONTEND=noninteractive \
|
||||||
@@ -76,7 +76,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.17
|
FROM docker.io/library/alpine:3.17
|
||||||
|
|
||||||
ENV ROCKET_PROFILE="release" \
|
ENV ROCKET_PROFILE="release" \
|
||||||
ROCKET_ADDRESS=0.0.0.0 \
|
ROCKET_ADDRESS=0.0.0.0 \
|
||||||
|
@@ -15,18 +15,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:v2023.3.0b
|
# $ docker pull docker.io/vaultwarden/web-vault:v2023.5.0
|
||||||
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2023.3.0b
|
# $ docker image inspect --format "{{.RepoDigests}}" docker.io/vaultwarden/web-vault:v2023.5.0
|
||||||
# [vaultwarden/web-vault@sha256:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee]
|
# [docker.io/vaultwarden/web-vault@sha256:e5b5e99d132d50dc73176afb65f41cf3b834fb06bfa1d621ac16c705c3f10085]
|
||||||
#
|
#
|
||||||
# - 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:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee
|
# $ docker image inspect --format "{{.RepoTags}}" docker.io/vaultwarden/web-vault@sha256:e5b5e99d132d50dc73176afb65f41cf3b834fb06bfa1d621ac16c705c3f10085
|
||||||
# [vaultwarden/web-vault:v2023.3.0b]
|
# [docker.io/vaultwarden/web-vault:v2023.5.0]
|
||||||
#
|
#
|
||||||
FROM vaultwarden/web-vault@sha256:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee as vault
|
FROM docker.io/vaultwarden/web-vault@sha256:e5b5e99d132d50dc73176afb65f41cf3b834fb06bfa1d621ac16c705c3f10085 as vault
|
||||||
|
|
||||||
########################## BUILD IMAGE ##########################
|
########################## BUILD IMAGE ##########################
|
||||||
FROM rust:1.68.2-bullseye as build
|
FROM docker.io/library/rust:1.70.0-bullseye as build
|
||||||
|
|
||||||
# Build time options to avoid dpkg warnings and help with reproducible builds.
|
# Build time options to avoid dpkg warnings and help with reproducible builds.
|
||||||
ENV DEBIAN_FRONTEND=noninteractive \
|
ENV DEBIAN_FRONTEND=noninteractive \
|
||||||
@@ -99,7 +99,7 @@ RUN cargo build --features ${DB} --release --target=aarch64-unknown-linux-gnu
|
|||||||
######################## 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-debian:bullseye
|
FROM docker.io/balenalib/aarch64-debian:bullseye
|
||||||
|
|
||||||
ENV ROCKET_PROFILE="release" \
|
ENV ROCKET_PROFILE="release" \
|
||||||
ROCKET_ADDRESS=0.0.0.0 \
|
ROCKET_ADDRESS=0.0.0.0 \
|
||||||
|
@@ -15,18 +15,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:v2023.3.0b
|
# $ docker pull docker.io/vaultwarden/web-vault:v2023.5.0
|
||||||
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2023.3.0b
|
# $ docker image inspect --format "{{.RepoDigests}}" docker.io/vaultwarden/web-vault:v2023.5.0
|
||||||
# [vaultwarden/web-vault@sha256:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee]
|
# [docker.io/vaultwarden/web-vault@sha256:e5b5e99d132d50dc73176afb65f41cf3b834fb06bfa1d621ac16c705c3f10085]
|
||||||
#
|
#
|
||||||
# - 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:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee
|
# $ docker image inspect --format "{{.RepoTags}}" docker.io/vaultwarden/web-vault@sha256:e5b5e99d132d50dc73176afb65f41cf3b834fb06bfa1d621ac16c705c3f10085
|
||||||
# [vaultwarden/web-vault:v2023.3.0b]
|
# [docker.io/vaultwarden/web-vault:v2023.5.0]
|
||||||
#
|
#
|
||||||
FROM vaultwarden/web-vault@sha256:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee as vault
|
FROM docker.io/vaultwarden/web-vault@sha256:e5b5e99d132d50dc73176afb65f41cf3b834fb06bfa1d621ac16c705c3f10085 as vault
|
||||||
|
|
||||||
########################## BUILD IMAGE ##########################
|
########################## BUILD IMAGE ##########################
|
||||||
FROM blackdex/rust-musl:aarch64-musl-stable-1.68.2 as build
|
FROM docker.io/blackdex/rust-musl:aarch64-musl-stable-1.70.0 as build
|
||||||
|
|
||||||
# Build time options to avoid dpkg warnings and help with reproducible builds.
|
# Build time options to avoid dpkg warnings and help with reproducible builds.
|
||||||
ENV DEBIAN_FRONTEND=noninteractive \
|
ENV DEBIAN_FRONTEND=noninteractive \
|
||||||
@@ -76,7 +76,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.17
|
FROM docker.io/balenalib/aarch64-alpine:3.17
|
||||||
|
|
||||||
ENV ROCKET_PROFILE="release" \
|
ENV ROCKET_PROFILE="release" \
|
||||||
ROCKET_ADDRESS=0.0.0.0 \
|
ROCKET_ADDRESS=0.0.0.0 \
|
||||||
|
@@ -15,18 +15,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:v2023.3.0b
|
# $ docker pull docker.io/vaultwarden/web-vault:v2023.5.0
|
||||||
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2023.3.0b
|
# $ docker image inspect --format "{{.RepoDigests}}" docker.io/vaultwarden/web-vault:v2023.5.0
|
||||||
# [vaultwarden/web-vault@sha256:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee]
|
# [docker.io/vaultwarden/web-vault@sha256:e5b5e99d132d50dc73176afb65f41cf3b834fb06bfa1d621ac16c705c3f10085]
|
||||||
#
|
#
|
||||||
# - 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:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee
|
# $ docker image inspect --format "{{.RepoTags}}" docker.io/vaultwarden/web-vault@sha256:e5b5e99d132d50dc73176afb65f41cf3b834fb06bfa1d621ac16c705c3f10085
|
||||||
# [vaultwarden/web-vault:v2023.3.0b]
|
# [docker.io/vaultwarden/web-vault:v2023.5.0]
|
||||||
#
|
#
|
||||||
FROM vaultwarden/web-vault@sha256:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee as vault
|
FROM docker.io/vaultwarden/web-vault@sha256:e5b5e99d132d50dc73176afb65f41cf3b834fb06bfa1d621ac16c705c3f10085 as vault
|
||||||
|
|
||||||
########################## BUILD IMAGE ##########################
|
########################## BUILD IMAGE ##########################
|
||||||
FROM rust:1.68.2-bullseye as build
|
FROM docker.io/library/rust:1.70.0-bullseye as build
|
||||||
|
|
||||||
# Build time options to avoid dpkg warnings and help with reproducible builds.
|
# Build time options to avoid dpkg warnings and help with reproducible builds.
|
||||||
ENV DEBIAN_FRONTEND=noninteractive \
|
ENV DEBIAN_FRONTEND=noninteractive \
|
||||||
@@ -99,7 +99,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-debian:bullseye
|
FROM docker.io/balenalib/aarch64-debian:bullseye
|
||||||
|
|
||||||
ENV ROCKET_PROFILE="release" \
|
ENV ROCKET_PROFILE="release" \
|
||||||
ROCKET_ADDRESS=0.0.0.0 \
|
ROCKET_ADDRESS=0.0.0.0 \
|
||||||
|
@@ -15,18 +15,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:v2023.3.0b
|
# $ docker pull docker.io/vaultwarden/web-vault:v2023.5.0
|
||||||
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2023.3.0b
|
# $ docker image inspect --format "{{.RepoDigests}}" docker.io/vaultwarden/web-vault:v2023.5.0
|
||||||
# [vaultwarden/web-vault@sha256:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee]
|
# [docker.io/vaultwarden/web-vault@sha256:e5b5e99d132d50dc73176afb65f41cf3b834fb06bfa1d621ac16c705c3f10085]
|
||||||
#
|
#
|
||||||
# - 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:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee
|
# $ docker image inspect --format "{{.RepoTags}}" docker.io/vaultwarden/web-vault@sha256:e5b5e99d132d50dc73176afb65f41cf3b834fb06bfa1d621ac16c705c3f10085
|
||||||
# [vaultwarden/web-vault:v2023.3.0b]
|
# [docker.io/vaultwarden/web-vault:v2023.5.0]
|
||||||
#
|
#
|
||||||
FROM vaultwarden/web-vault@sha256:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee as vault
|
FROM docker.io/vaultwarden/web-vault@sha256:e5b5e99d132d50dc73176afb65f41cf3b834fb06bfa1d621ac16c705c3f10085 as vault
|
||||||
|
|
||||||
########################## BUILD IMAGE ##########################
|
########################## BUILD IMAGE ##########################
|
||||||
FROM blackdex/rust-musl:aarch64-musl-stable-1.68.2 as build
|
FROM docker.io/blackdex/rust-musl:aarch64-musl-stable-1.70.0 as build
|
||||||
|
|
||||||
# Build time options to avoid dpkg warnings and help with reproducible builds.
|
# Build time options to avoid dpkg warnings and help with reproducible builds.
|
||||||
ENV DEBIAN_FRONTEND=noninteractive \
|
ENV DEBIAN_FRONTEND=noninteractive \
|
||||||
@@ -76,7 +76,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.17
|
FROM docker.io/balenalib/aarch64-alpine:3.17
|
||||||
|
|
||||||
ENV ROCKET_PROFILE="release" \
|
ENV ROCKET_PROFILE="release" \
|
||||||
ROCKET_ADDRESS=0.0.0.0 \
|
ROCKET_ADDRESS=0.0.0.0 \
|
||||||
|
@@ -15,18 +15,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:v2023.3.0b
|
# $ docker pull docker.io/vaultwarden/web-vault:v2023.5.0
|
||||||
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2023.3.0b
|
# $ docker image inspect --format "{{.RepoDigests}}" docker.io/vaultwarden/web-vault:v2023.5.0
|
||||||
# [vaultwarden/web-vault@sha256:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee]
|
# [docker.io/vaultwarden/web-vault@sha256:e5b5e99d132d50dc73176afb65f41cf3b834fb06bfa1d621ac16c705c3f10085]
|
||||||
#
|
#
|
||||||
# - 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:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee
|
# $ docker image inspect --format "{{.RepoTags}}" docker.io/vaultwarden/web-vault@sha256:e5b5e99d132d50dc73176afb65f41cf3b834fb06bfa1d621ac16c705c3f10085
|
||||||
# [vaultwarden/web-vault:v2023.3.0b]
|
# [docker.io/vaultwarden/web-vault:v2023.5.0]
|
||||||
#
|
#
|
||||||
FROM vaultwarden/web-vault@sha256:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee as vault
|
FROM docker.io/vaultwarden/web-vault@sha256:e5b5e99d132d50dc73176afb65f41cf3b834fb06bfa1d621ac16c705c3f10085 as vault
|
||||||
|
|
||||||
########################## BUILD IMAGE ##########################
|
########################## BUILD IMAGE ##########################
|
||||||
FROM rust:1.68.2-bullseye as build
|
FROM docker.io/library/rust:1.70.0-bullseye as build
|
||||||
|
|
||||||
# Build time options to avoid dpkg warnings and help with reproducible builds.
|
# Build time options to avoid dpkg warnings and help with reproducible builds.
|
||||||
ENV DEBIAN_FRONTEND=noninteractive \
|
ENV DEBIAN_FRONTEND=noninteractive \
|
||||||
@@ -99,7 +99,7 @@ RUN cargo build --features ${DB} --release --target=arm-unknown-linux-gnueabi
|
|||||||
######################## 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-debian:bullseye
|
FROM docker.io/balenalib/rpi-debian:bullseye
|
||||||
|
|
||||||
ENV ROCKET_PROFILE="release" \
|
ENV ROCKET_PROFILE="release" \
|
||||||
ROCKET_ADDRESS=0.0.0.0 \
|
ROCKET_ADDRESS=0.0.0.0 \
|
||||||
|
@@ -15,18 +15,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:v2023.3.0b
|
# $ docker pull docker.io/vaultwarden/web-vault:v2023.5.0
|
||||||
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2023.3.0b
|
# $ docker image inspect --format "{{.RepoDigests}}" docker.io/vaultwarden/web-vault:v2023.5.0
|
||||||
# [vaultwarden/web-vault@sha256:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee]
|
# [docker.io/vaultwarden/web-vault@sha256:e5b5e99d132d50dc73176afb65f41cf3b834fb06bfa1d621ac16c705c3f10085]
|
||||||
#
|
#
|
||||||
# - 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:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee
|
# $ docker image inspect --format "{{.RepoTags}}" docker.io/vaultwarden/web-vault@sha256:e5b5e99d132d50dc73176afb65f41cf3b834fb06bfa1d621ac16c705c3f10085
|
||||||
# [vaultwarden/web-vault:v2023.3.0b]
|
# [docker.io/vaultwarden/web-vault:v2023.5.0]
|
||||||
#
|
#
|
||||||
FROM vaultwarden/web-vault@sha256:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee as vault
|
FROM docker.io/vaultwarden/web-vault@sha256:e5b5e99d132d50dc73176afb65f41cf3b834fb06bfa1d621ac16c705c3f10085 as vault
|
||||||
|
|
||||||
########################## BUILD IMAGE ##########################
|
########################## BUILD IMAGE ##########################
|
||||||
FROM blackdex/rust-musl:arm-musleabi-stable-1.68.2 as build
|
FROM docker.io/blackdex/rust-musl:arm-musleabi-stable-1.70.0 as build
|
||||||
|
|
||||||
# Build time options to avoid dpkg warnings and help with reproducible builds.
|
# Build time options to avoid dpkg warnings and help with reproducible builds.
|
||||||
ENV DEBIAN_FRONTEND=noninteractive \
|
ENV DEBIAN_FRONTEND=noninteractive \
|
||||||
@@ -78,7 +78,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.17
|
FROM docker.io/balenalib/rpi-alpine:3.17
|
||||||
|
|
||||||
ENV ROCKET_PROFILE="release" \
|
ENV ROCKET_PROFILE="release" \
|
||||||
ROCKET_ADDRESS=0.0.0.0 \
|
ROCKET_ADDRESS=0.0.0.0 \
|
||||||
|
@@ -15,18 +15,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:v2023.3.0b
|
# $ docker pull docker.io/vaultwarden/web-vault:v2023.5.0
|
||||||
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2023.3.0b
|
# $ docker image inspect --format "{{.RepoDigests}}" docker.io/vaultwarden/web-vault:v2023.5.0
|
||||||
# [vaultwarden/web-vault@sha256:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee]
|
# [docker.io/vaultwarden/web-vault@sha256:e5b5e99d132d50dc73176afb65f41cf3b834fb06bfa1d621ac16c705c3f10085]
|
||||||
#
|
#
|
||||||
# - 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:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee
|
# $ docker image inspect --format "{{.RepoTags}}" docker.io/vaultwarden/web-vault@sha256:e5b5e99d132d50dc73176afb65f41cf3b834fb06bfa1d621ac16c705c3f10085
|
||||||
# [vaultwarden/web-vault:v2023.3.0b]
|
# [docker.io/vaultwarden/web-vault:v2023.5.0]
|
||||||
#
|
#
|
||||||
FROM vaultwarden/web-vault@sha256:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee as vault
|
FROM docker.io/vaultwarden/web-vault@sha256:e5b5e99d132d50dc73176afb65f41cf3b834fb06bfa1d621ac16c705c3f10085 as vault
|
||||||
|
|
||||||
########################## BUILD IMAGE ##########################
|
########################## BUILD IMAGE ##########################
|
||||||
FROM rust:1.68.2-bullseye as build
|
FROM docker.io/library/rust:1.70.0-bullseye as build
|
||||||
|
|
||||||
# Build time options to avoid dpkg warnings and help with reproducible builds.
|
# Build time options to avoid dpkg warnings and help with reproducible builds.
|
||||||
ENV DEBIAN_FRONTEND=noninteractive \
|
ENV DEBIAN_FRONTEND=noninteractive \
|
||||||
@@ -99,7 +99,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-debian:bullseye
|
FROM docker.io/balenalib/rpi-debian:bullseye
|
||||||
|
|
||||||
ENV ROCKET_PROFILE="release" \
|
ENV ROCKET_PROFILE="release" \
|
||||||
ROCKET_ADDRESS=0.0.0.0 \
|
ROCKET_ADDRESS=0.0.0.0 \
|
||||||
|
@@ -15,18 +15,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:v2023.3.0b
|
# $ docker pull docker.io/vaultwarden/web-vault:v2023.5.0
|
||||||
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2023.3.0b
|
# $ docker image inspect --format "{{.RepoDigests}}" docker.io/vaultwarden/web-vault:v2023.5.0
|
||||||
# [vaultwarden/web-vault@sha256:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee]
|
# [docker.io/vaultwarden/web-vault@sha256:e5b5e99d132d50dc73176afb65f41cf3b834fb06bfa1d621ac16c705c3f10085]
|
||||||
#
|
#
|
||||||
# - 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:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee
|
# $ docker image inspect --format "{{.RepoTags}}" docker.io/vaultwarden/web-vault@sha256:e5b5e99d132d50dc73176afb65f41cf3b834fb06bfa1d621ac16c705c3f10085
|
||||||
# [vaultwarden/web-vault:v2023.3.0b]
|
# [docker.io/vaultwarden/web-vault:v2023.5.0]
|
||||||
#
|
#
|
||||||
FROM vaultwarden/web-vault@sha256:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee as vault
|
FROM docker.io/vaultwarden/web-vault@sha256:e5b5e99d132d50dc73176afb65f41cf3b834fb06bfa1d621ac16c705c3f10085 as vault
|
||||||
|
|
||||||
########################## BUILD IMAGE ##########################
|
########################## BUILD IMAGE ##########################
|
||||||
FROM blackdex/rust-musl:arm-musleabi-stable-1.68.2 as build
|
FROM docker.io/blackdex/rust-musl:arm-musleabi-stable-1.70.0 as build
|
||||||
|
|
||||||
# Build time options to avoid dpkg warnings and help with reproducible builds.
|
# Build time options to avoid dpkg warnings and help with reproducible builds.
|
||||||
ENV DEBIAN_FRONTEND=noninteractive \
|
ENV DEBIAN_FRONTEND=noninteractive \
|
||||||
@@ -78,7 +78,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.17
|
FROM docker.io/balenalib/rpi-alpine:3.17
|
||||||
|
|
||||||
ENV ROCKET_PROFILE="release" \
|
ENV ROCKET_PROFILE="release" \
|
||||||
ROCKET_ADDRESS=0.0.0.0 \
|
ROCKET_ADDRESS=0.0.0.0 \
|
||||||
|
@@ -15,18 +15,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:v2023.3.0b
|
# $ docker pull docker.io/vaultwarden/web-vault:v2023.5.0
|
||||||
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2023.3.0b
|
# $ docker image inspect --format "{{.RepoDigests}}" docker.io/vaultwarden/web-vault:v2023.5.0
|
||||||
# [vaultwarden/web-vault@sha256:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee]
|
# [docker.io/vaultwarden/web-vault@sha256:e5b5e99d132d50dc73176afb65f41cf3b834fb06bfa1d621ac16c705c3f10085]
|
||||||
#
|
#
|
||||||
# - 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:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee
|
# $ docker image inspect --format "{{.RepoTags}}" docker.io/vaultwarden/web-vault@sha256:e5b5e99d132d50dc73176afb65f41cf3b834fb06bfa1d621ac16c705c3f10085
|
||||||
# [vaultwarden/web-vault:v2023.3.0b]
|
# [docker.io/vaultwarden/web-vault:v2023.5.0]
|
||||||
#
|
#
|
||||||
FROM vaultwarden/web-vault@sha256:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee as vault
|
FROM docker.io/vaultwarden/web-vault@sha256:e5b5e99d132d50dc73176afb65f41cf3b834fb06bfa1d621ac16c705c3f10085 as vault
|
||||||
|
|
||||||
########################## BUILD IMAGE ##########################
|
########################## BUILD IMAGE ##########################
|
||||||
FROM rust:1.68.2-bullseye as build
|
FROM docker.io/library/rust:1.70.0-bullseye as build
|
||||||
|
|
||||||
# Build time options to avoid dpkg warnings and help with reproducible builds.
|
# Build time options to avoid dpkg warnings and help with reproducible builds.
|
||||||
ENV DEBIAN_FRONTEND=noninteractive \
|
ENV DEBIAN_FRONTEND=noninteractive \
|
||||||
@@ -99,7 +99,7 @@ RUN cargo build --features ${DB} --release --target=armv7-unknown-linux-gnueabih
|
|||||||
######################## 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-debian:bullseye
|
FROM docker.io/balenalib/armv7hf-debian:bullseye
|
||||||
|
|
||||||
ENV ROCKET_PROFILE="release" \
|
ENV ROCKET_PROFILE="release" \
|
||||||
ROCKET_ADDRESS=0.0.0.0 \
|
ROCKET_ADDRESS=0.0.0.0 \
|
||||||
|
@@ -15,18 +15,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:v2023.3.0b
|
# $ docker pull docker.io/vaultwarden/web-vault:v2023.5.0
|
||||||
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2023.3.0b
|
# $ docker image inspect --format "{{.RepoDigests}}" docker.io/vaultwarden/web-vault:v2023.5.0
|
||||||
# [vaultwarden/web-vault@sha256:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee]
|
# [docker.io/vaultwarden/web-vault@sha256:e5b5e99d132d50dc73176afb65f41cf3b834fb06bfa1d621ac16c705c3f10085]
|
||||||
#
|
#
|
||||||
# - 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:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee
|
# $ docker image inspect --format "{{.RepoTags}}" docker.io/vaultwarden/web-vault@sha256:e5b5e99d132d50dc73176afb65f41cf3b834fb06bfa1d621ac16c705c3f10085
|
||||||
# [vaultwarden/web-vault:v2023.3.0b]
|
# [docker.io/vaultwarden/web-vault:v2023.5.0]
|
||||||
#
|
#
|
||||||
FROM vaultwarden/web-vault@sha256:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee as vault
|
FROM docker.io/vaultwarden/web-vault@sha256:e5b5e99d132d50dc73176afb65f41cf3b834fb06bfa1d621ac16c705c3f10085 as vault
|
||||||
|
|
||||||
########################## BUILD IMAGE ##########################
|
########################## BUILD IMAGE ##########################
|
||||||
FROM blackdex/rust-musl:armv7-musleabihf-stable-1.68.2 as build
|
FROM docker.io/blackdex/rust-musl:armv7-musleabihf-stable-1.70.0 as build
|
||||||
|
|
||||||
# Build time options to avoid dpkg warnings and help with reproducible builds.
|
# Build time options to avoid dpkg warnings and help with reproducible builds.
|
||||||
ENV DEBIAN_FRONTEND=noninteractive \
|
ENV DEBIAN_FRONTEND=noninteractive \
|
||||||
@@ -76,7 +76,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.17
|
FROM docker.io/balenalib/armv7hf-alpine:3.17
|
||||||
|
|
||||||
ENV ROCKET_PROFILE="release" \
|
ENV ROCKET_PROFILE="release" \
|
||||||
ROCKET_ADDRESS=0.0.0.0 \
|
ROCKET_ADDRESS=0.0.0.0 \
|
||||||
|
@@ -15,18 +15,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:v2023.3.0b
|
# $ docker pull docker.io/vaultwarden/web-vault:v2023.5.0
|
||||||
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2023.3.0b
|
# $ docker image inspect --format "{{.RepoDigests}}" docker.io/vaultwarden/web-vault:v2023.5.0
|
||||||
# [vaultwarden/web-vault@sha256:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee]
|
# [docker.io/vaultwarden/web-vault@sha256:e5b5e99d132d50dc73176afb65f41cf3b834fb06bfa1d621ac16c705c3f10085]
|
||||||
#
|
#
|
||||||
# - 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:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee
|
# $ docker image inspect --format "{{.RepoTags}}" docker.io/vaultwarden/web-vault@sha256:e5b5e99d132d50dc73176afb65f41cf3b834fb06bfa1d621ac16c705c3f10085
|
||||||
# [vaultwarden/web-vault:v2023.3.0b]
|
# [docker.io/vaultwarden/web-vault:v2023.5.0]
|
||||||
#
|
#
|
||||||
FROM vaultwarden/web-vault@sha256:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee as vault
|
FROM docker.io/vaultwarden/web-vault@sha256:e5b5e99d132d50dc73176afb65f41cf3b834fb06bfa1d621ac16c705c3f10085 as vault
|
||||||
|
|
||||||
########################## BUILD IMAGE ##########################
|
########################## BUILD IMAGE ##########################
|
||||||
FROM rust:1.68.2-bullseye as build
|
FROM docker.io/library/rust:1.70.0-bullseye as build
|
||||||
|
|
||||||
# Build time options to avoid dpkg warnings and help with reproducible builds.
|
# Build time options to avoid dpkg warnings and help with reproducible builds.
|
||||||
ENV DEBIAN_FRONTEND=noninteractive \
|
ENV DEBIAN_FRONTEND=noninteractive \
|
||||||
@@ -99,7 +99,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-debian:bullseye
|
FROM docker.io/balenalib/armv7hf-debian:bullseye
|
||||||
|
|
||||||
ENV ROCKET_PROFILE="release" \
|
ENV ROCKET_PROFILE="release" \
|
||||||
ROCKET_ADDRESS=0.0.0.0 \
|
ROCKET_ADDRESS=0.0.0.0 \
|
||||||
|
@@ -15,18 +15,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:v2023.3.0b
|
# $ docker pull docker.io/vaultwarden/web-vault:v2023.5.0
|
||||||
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2023.3.0b
|
# $ docker image inspect --format "{{.RepoDigests}}" docker.io/vaultwarden/web-vault:v2023.5.0
|
||||||
# [vaultwarden/web-vault@sha256:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee]
|
# [docker.io/vaultwarden/web-vault@sha256:e5b5e99d132d50dc73176afb65f41cf3b834fb06bfa1d621ac16c705c3f10085]
|
||||||
#
|
#
|
||||||
# - 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:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee
|
# $ docker image inspect --format "{{.RepoTags}}" docker.io/vaultwarden/web-vault@sha256:e5b5e99d132d50dc73176afb65f41cf3b834fb06bfa1d621ac16c705c3f10085
|
||||||
# [vaultwarden/web-vault:v2023.3.0b]
|
# [docker.io/vaultwarden/web-vault:v2023.5.0]
|
||||||
#
|
#
|
||||||
FROM vaultwarden/web-vault@sha256:aa6ba791911a815ea570ec2ddc59992481c6ba8fbb65eed4f7074b463430d3ee as vault
|
FROM docker.io/vaultwarden/web-vault@sha256:e5b5e99d132d50dc73176afb65f41cf3b834fb06bfa1d621ac16c705c3f10085 as vault
|
||||||
|
|
||||||
########################## BUILD IMAGE ##########################
|
########################## BUILD IMAGE ##########################
|
||||||
FROM blackdex/rust-musl:armv7-musleabihf-stable-1.68.2 as build
|
FROM docker.io/blackdex/rust-musl:armv7-musleabihf-stable-1.70.0 as build
|
||||||
|
|
||||||
# Build time options to avoid dpkg warnings and help with reproducible builds.
|
# Build time options to avoid dpkg warnings and help with reproducible builds.
|
||||||
ENV DEBIAN_FRONTEND=noninteractive \
|
ENV DEBIAN_FRONTEND=noninteractive \
|
||||||
@@ -76,7 +76,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.17
|
FROM docker.io/balenalib/armv7hf-alpine:3.17
|
||||||
|
|
||||||
ENV ROCKET_PROFILE="release" \
|
ENV ROCKET_PROFILE="release" \
|
||||||
ROCKET_ADDRESS=0.0.0.0 \
|
ROCKET_ADDRESS=0.0.0.0 \
|
||||||
|
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE devices ADD COLUMN push_uuid TEXT;
|
@@ -0,0 +1,10 @@
|
|||||||
|
CREATE TABLE organization_api_key (
|
||||||
|
uuid CHAR(36) NOT NULL,
|
||||||
|
org_uuid CHAR(36) NOT NULL REFERENCES organizations(uuid),
|
||||||
|
atype INTEGER NOT NULL,
|
||||||
|
api_key VARCHAR(255) NOT NULL,
|
||||||
|
revision_date DATETIME NOT NULL,
|
||||||
|
PRIMARY KEY(uuid, org_uuid)
|
||||||
|
);
|
||||||
|
|
||||||
|
ALTER TABLE users ADD COLUMN external_id TEXT;
|
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE collections ADD COLUMN external_id TEXT;
|
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE devices ADD COLUMN push_uuid TEXT;
|
@@ -0,0 +1,10 @@
|
|||||||
|
CREATE TABLE organization_api_key (
|
||||||
|
uuid CHAR(36) NOT NULL,
|
||||||
|
org_uuid CHAR(36) NOT NULL REFERENCES organizations(uuid),
|
||||||
|
atype INTEGER NOT NULL,
|
||||||
|
api_key VARCHAR(255),
|
||||||
|
revision_date TIMESTAMP NOT NULL,
|
||||||
|
PRIMARY KEY(uuid, org_uuid)
|
||||||
|
);
|
||||||
|
|
||||||
|
ALTER TABLE users ADD COLUMN external_id TEXT;
|
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE collections ADD COLUMN external_id TEXT;
|
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE devices ADD COLUMN push_uuid TEXT;
|
@@ -0,0 +1,11 @@
|
|||||||
|
CREATE TABLE organization_api_key (
|
||||||
|
uuid TEXT NOT NULL,
|
||||||
|
org_uuid TEXT NOT NULL,
|
||||||
|
atype INTEGER NOT NULL,
|
||||||
|
api_key TEXT NOT NULL,
|
||||||
|
revision_date DATETIME NOT NULL,
|
||||||
|
PRIMARY KEY(uuid, org_uuid),
|
||||||
|
FOREIGN KEY(org_uuid) REFERENCES organizations(uuid)
|
||||||
|
);
|
||||||
|
|
||||||
|
ALTER TABLE users ADD COLUMN external_id TEXT;
|
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE collections ADD COLUMN external_id TEXT;
|
@@ -1 +1 @@
|
|||||||
1.68.2
|
1.70.0
|
||||||
|
@@ -13,7 +13,7 @@ use rocket::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
api::{core::log_event, ApiResult, EmptyResult, JsonResult, Notify, NumberOrString},
|
api::{core::log_event, unregister_push_device, ApiResult, EmptyResult, JsonResult, Notify, NumberOrString},
|
||||||
auth::{decode_admin, encode_jwt, generate_admin_claims, ClientIp},
|
auth::{decode_admin, encode_jwt, generate_admin_claims, ClientIp},
|
||||||
config::ConfigBuilder,
|
config::ConfigBuilder,
|
||||||
db::{backup_database, get_sql_server_version, models::*, DbConn, DbConnType},
|
db::{backup_database, get_sql_server_version, models::*, DbConn, DbConnType},
|
||||||
@@ -36,6 +36,7 @@ pub fn routes() -> Vec<Route> {
|
|||||||
get_user_by_mail_json,
|
get_user_by_mail_json,
|
||||||
post_admin_login,
|
post_admin_login,
|
||||||
admin_page,
|
admin_page,
|
||||||
|
admin_page_login,
|
||||||
invite_user,
|
invite_user,
|
||||||
logout,
|
logout,
|
||||||
delete_user,
|
delete_user,
|
||||||
@@ -256,6 +257,11 @@ fn admin_page(_token: AdminToken) -> ApiResult<Html<String>> {
|
|||||||
render_admin_page()
|
render_admin_page()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[get("/", rank = 2)]
|
||||||
|
fn admin_page_login() -> ApiResult<Html<String>> {
|
||||||
|
render_admin_login(None, None)
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
struct InviteData {
|
struct InviteData {
|
||||||
@@ -349,8 +355,8 @@ async fn users_overview(_token: AdminToken, mut conn: DbConn) -> ApiResult<Html<
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[get("/users/by-mail/<mail>")]
|
#[get("/users/by-mail/<mail>")]
|
||||||
async fn get_user_by_mail_json(mail: String, _token: AdminToken, mut conn: DbConn) -> JsonResult {
|
async fn get_user_by_mail_json(mail: &str, _token: AdminToken, mut conn: DbConn) -> JsonResult {
|
||||||
if let Some(u) = User::find_by_mail(&mail, &mut conn).await {
|
if let Some(u) = User::find_by_mail(mail, &mut conn).await {
|
||||||
let mut usr = u.to_json(&mut conn).await;
|
let mut usr = u.to_json(&mut conn).await;
|
||||||
usr["UserEnabled"] = json!(u.enabled);
|
usr["UserEnabled"] = json!(u.enabled);
|
||||||
usr["CreatedAt"] = json!(format_naive_datetime_local(&u.created_at, DT_FMT));
|
usr["CreatedAt"] = json!(format_naive_datetime_local(&u.created_at, DT_FMT));
|
||||||
@@ -361,8 +367,8 @@ async fn get_user_by_mail_json(mail: String, _token: AdminToken, mut conn: DbCon
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[get("/users/<uuid>")]
|
#[get("/users/<uuid>")]
|
||||||
async fn get_user_json(uuid: String, _token: AdminToken, mut conn: DbConn) -> JsonResult {
|
async fn get_user_json(uuid: &str, _token: AdminToken, mut conn: DbConn) -> JsonResult {
|
||||||
let u = get_user_or_404(&uuid, &mut conn).await?;
|
let u = get_user_or_404(uuid, &mut conn).await?;
|
||||||
let mut usr = u.to_json(&mut conn).await;
|
let mut usr = u.to_json(&mut conn).await;
|
||||||
usr["UserEnabled"] = json!(u.enabled);
|
usr["UserEnabled"] = json!(u.enabled);
|
||||||
usr["CreatedAt"] = json!(format_naive_datetime_local(&u.created_at, DT_FMT));
|
usr["CreatedAt"] = json!(format_naive_datetime_local(&u.created_at, DT_FMT));
|
||||||
@@ -370,18 +376,18 @@ async fn get_user_json(uuid: String, _token: AdminToken, mut conn: DbConn) -> Js
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[post("/users/<uuid>/delete")]
|
#[post("/users/<uuid>/delete")]
|
||||||
async fn delete_user(uuid: String, token: AdminToken, mut conn: DbConn) -> EmptyResult {
|
async fn delete_user(uuid: &str, token: AdminToken, mut conn: DbConn) -> EmptyResult {
|
||||||
let user = get_user_or_404(&uuid, &mut conn).await?;
|
let user = get_user_or_404(uuid, &mut conn).await?;
|
||||||
|
|
||||||
// Get the user_org records before deleting the actual user
|
// Get the user_org records before deleting the actual user
|
||||||
let user_orgs = UserOrganization::find_any_state_by_user(&uuid, &mut conn).await;
|
let user_orgs = UserOrganization::find_any_state_by_user(uuid, &mut conn).await;
|
||||||
let res = user.delete(&mut conn).await;
|
let res = user.delete(&mut conn).await;
|
||||||
|
|
||||||
for user_org in user_orgs {
|
for user_org in user_orgs {
|
||||||
log_event(
|
log_event(
|
||||||
EventType::OrganizationUserRemoved as i32,
|
EventType::OrganizationUserRemoved as i32,
|
||||||
&user_org.uuid,
|
&user_org.uuid,
|
||||||
user_org.org_uuid,
|
&user_org.org_uuid,
|
||||||
String::from(ACTING_ADMIN_USER),
|
String::from(ACTING_ADMIN_USER),
|
||||||
14, // Use UnknownBrowser type
|
14, // Use UnknownBrowser type
|
||||||
&token.ip.ip,
|
&token.ip.ip,
|
||||||
@@ -394,21 +400,29 @@ async fn delete_user(uuid: String, token: AdminToken, mut conn: DbConn) -> Empty
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[post("/users/<uuid>/deauth")]
|
#[post("/users/<uuid>/deauth")]
|
||||||
async fn deauth_user(uuid: String, _token: AdminToken, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult {
|
async fn deauth_user(uuid: &str, _token: AdminToken, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult {
|
||||||
let mut user = get_user_or_404(&uuid, &mut conn).await?;
|
let mut user = get_user_or_404(uuid, &mut conn).await?;
|
||||||
Device::delete_all_by_user(&user.uuid, &mut conn).await?;
|
|
||||||
user.reset_security_stamp();
|
|
||||||
|
|
||||||
let save_result = user.save(&mut conn).await;
|
|
||||||
|
|
||||||
nt.send_logout(&user, None).await;
|
nt.send_logout(&user, None).await;
|
||||||
|
|
||||||
save_result
|
if CONFIG.push_enabled() {
|
||||||
|
for device in Device::find_push_devices_by_user(&user.uuid, &mut conn).await {
|
||||||
|
match unregister_push_device(device.uuid).await {
|
||||||
|
Ok(r) => r,
|
||||||
|
Err(e) => error!("Unable to unregister devices from Bitwarden server: {}", e),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Device::delete_all_by_user(&user.uuid, &mut conn).await?;
|
||||||
|
user.reset_security_stamp();
|
||||||
|
|
||||||
|
user.save(&mut conn).await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/users/<uuid>/disable")]
|
#[post("/users/<uuid>/disable")]
|
||||||
async fn disable_user(uuid: String, _token: AdminToken, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult {
|
async fn disable_user(uuid: &str, _token: AdminToken, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult {
|
||||||
let mut user = get_user_or_404(&uuid, &mut conn).await?;
|
let mut user = get_user_or_404(uuid, &mut conn).await?;
|
||||||
Device::delete_all_by_user(&user.uuid, &mut conn).await?;
|
Device::delete_all_by_user(&user.uuid, &mut conn).await?;
|
||||||
user.reset_security_stamp();
|
user.reset_security_stamp();
|
||||||
user.enabled = false;
|
user.enabled = false;
|
||||||
@@ -421,24 +435,24 @@ async fn disable_user(uuid: String, _token: AdminToken, mut conn: DbConn, nt: No
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[post("/users/<uuid>/enable")]
|
#[post("/users/<uuid>/enable")]
|
||||||
async fn enable_user(uuid: String, _token: AdminToken, mut conn: DbConn) -> EmptyResult {
|
async fn enable_user(uuid: &str, _token: AdminToken, mut conn: DbConn) -> EmptyResult {
|
||||||
let mut user = get_user_or_404(&uuid, &mut conn).await?;
|
let mut user = get_user_or_404(uuid, &mut conn).await?;
|
||||||
user.enabled = true;
|
user.enabled = true;
|
||||||
|
|
||||||
user.save(&mut conn).await
|
user.save(&mut conn).await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/users/<uuid>/remove-2fa")]
|
#[post("/users/<uuid>/remove-2fa")]
|
||||||
async fn remove_2fa(uuid: String, _token: AdminToken, mut conn: DbConn) -> EmptyResult {
|
async fn remove_2fa(uuid: &str, _token: AdminToken, mut conn: DbConn) -> EmptyResult {
|
||||||
let mut user = get_user_or_404(&uuid, &mut conn).await?;
|
let mut user = get_user_or_404(uuid, &mut conn).await?;
|
||||||
TwoFactor::delete_all_by_user(&user.uuid, &mut conn).await?;
|
TwoFactor::delete_all_by_user(&user.uuid, &mut conn).await?;
|
||||||
user.totp_recover = None;
|
user.totp_recover = None;
|
||||||
user.save(&mut conn).await
|
user.save(&mut conn).await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/users/<uuid>/invite/resend")]
|
#[post("/users/<uuid>/invite/resend")]
|
||||||
async fn resend_user_invite(uuid: String, _token: AdminToken, mut conn: DbConn) -> EmptyResult {
|
async fn resend_user_invite(uuid: &str, _token: AdminToken, mut conn: DbConn) -> EmptyResult {
|
||||||
if let Some(user) = User::find_by_uuid(&uuid, &mut conn).await {
|
if let Some(user) = User::find_by_uuid(uuid, &mut conn).await {
|
||||||
//TODO: replace this with user.status check when it will be available (PR#3397)
|
//TODO: replace this with user.status check when it will be available (PR#3397)
|
||||||
if !user.password_hash.is_empty() {
|
if !user.password_hash.is_empty() {
|
||||||
err_code!("User already accepted invitation", Status::BadRequest.code);
|
err_code!("User already accepted invitation", Status::BadRequest.code);
|
||||||
@@ -500,7 +514,7 @@ async fn update_user_org_type(data: Json<UserOrgTypeData>, token: AdminToken, mu
|
|||||||
log_event(
|
log_event(
|
||||||
EventType::OrganizationUserUpdated as i32,
|
EventType::OrganizationUserUpdated as i32,
|
||||||
&user_to_edit.uuid,
|
&user_to_edit.uuid,
|
||||||
data.org_uuid,
|
&data.org_uuid,
|
||||||
String::from(ACTING_ADMIN_USER),
|
String::from(ACTING_ADMIN_USER),
|
||||||
14, // Use UnknownBrowser type
|
14, // Use UnknownBrowser type
|
||||||
&token.ip.ip,
|
&token.ip.ip,
|
||||||
@@ -538,8 +552,8 @@ async fn organizations_overview(_token: AdminToken, mut conn: DbConn) -> ApiResu
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[post("/organizations/<uuid>/delete")]
|
#[post("/organizations/<uuid>/delete")]
|
||||||
async fn delete_organization(uuid: String, _token: AdminToken, mut conn: DbConn) -> EmptyResult {
|
async fn delete_organization(uuid: &str, _token: AdminToken, mut conn: DbConn) -> EmptyResult {
|
||||||
let org = Organization::find_by_uuid(&uuid, &mut conn).await.map_res("Organization doesn't exist")?;
|
let org = Organization::find_by_uuid(uuid, &mut conn).await.map_res("Organization doesn't exist")?;
|
||||||
org.delete(&mut conn).await
|
org.delete(&mut conn).await
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -761,7 +775,17 @@ impl<'r> FromRequest<'r> for AdminToken {
|
|||||||
|
|
||||||
let access_token = match cookies.get(COOKIE_NAME) {
|
let access_token = match cookies.get(COOKIE_NAME) {
|
||||||
Some(cookie) => cookie.value(),
|
Some(cookie) => cookie.value(),
|
||||||
None => return Outcome::Failure((Status::Unauthorized, "Unauthorized")),
|
None => {
|
||||||
|
let requested_page =
|
||||||
|
request.segments::<std::path::PathBuf>(0..).unwrap_or_default().display().to_string();
|
||||||
|
// When the requested page is empty, it is `/admin`, in that case, Forward, so it will render the login page
|
||||||
|
// Else, return a 401 failure, which will be caught
|
||||||
|
if requested_page.is_empty() {
|
||||||
|
return Outcome::Forward(Status::Unauthorized);
|
||||||
|
} else {
|
||||||
|
return Outcome::Failure((Status::Unauthorized, "Unauthorized"));
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if decode_admin(access_token).is_err() {
|
if decode_admin(access_token).is_err() {
|
||||||
|
@@ -4,7 +4,8 @@ use serde_json::Value;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
api::{
|
api::{
|
||||||
core::log_user_event, EmptyResult, JsonResult, JsonUpcase, Notify, NumberOrString, PasswordData, UpdateType,
|
core::log_user_event, register_push_device, unregister_push_device, EmptyResult, JsonResult, JsonUpcase,
|
||||||
|
Notify, NumberOrString, PasswordData, UpdateType,
|
||||||
},
|
},
|
||||||
auth::{decode_delete, decode_invite, decode_verify_email, Headers},
|
auth::{decode_delete, decode_invite, decode_verify_email, Headers},
|
||||||
crypto,
|
crypto,
|
||||||
@@ -35,6 +36,7 @@ pub fn routes() -> Vec<rocket::Route> {
|
|||||||
post_verify_email_token,
|
post_verify_email_token,
|
||||||
post_delete_recover,
|
post_delete_recover,
|
||||||
post_delete_recover_token,
|
post_delete_recover_token,
|
||||||
|
post_device_token,
|
||||||
delete_account,
|
delete_account,
|
||||||
post_delete_account,
|
post_delete_account,
|
||||||
revision_date,
|
revision_date,
|
||||||
@@ -46,6 +48,9 @@ pub fn routes() -> Vec<rocket::Route> {
|
|||||||
get_known_device,
|
get_known_device,
|
||||||
get_known_device_from_path,
|
get_known_device_from_path,
|
||||||
put_avatar,
|
put_avatar,
|
||||||
|
put_device_token,
|
||||||
|
put_clear_device_token,
|
||||||
|
post_clear_device_token,
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,7 +138,7 @@ pub async fn _register(data: JsonUpcase<RegisterData>, mut conn: DbConn) -> Json
|
|||||||
err!("Registration email does not match invite email")
|
err!("Registration email does not match invite email")
|
||||||
}
|
}
|
||||||
} else if Invitation::take(&email, &mut conn).await {
|
} else if Invitation::take(&email, &mut conn).await {
|
||||||
for mut user_org in UserOrganization::find_invited_by_user(&user.uuid, &mut conn).await.iter_mut() {
|
for user_org in UserOrganization::find_invited_by_user(&user.uuid, &mut conn).await.iter_mut() {
|
||||||
user_org.status = UserOrgStatus::Accepted as i32;
|
user_org.status = UserOrgStatus::Accepted as i32;
|
||||||
user_org.save(&mut conn).await?;
|
user_org.save(&mut conn).await?;
|
||||||
}
|
}
|
||||||
@@ -266,8 +271,8 @@ async fn put_avatar(data: JsonUpcase<AvatarData>, headers: Headers, mut conn: Db
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[get("/users/<uuid>/public-key")]
|
#[get("/users/<uuid>/public-key")]
|
||||||
async fn get_public_keys(uuid: String, _headers: Headers, mut conn: DbConn) -> JsonResult {
|
async fn get_public_keys(uuid: &str, _headers: Headers, mut conn: DbConn) -> JsonResult {
|
||||||
let user = match User::find_by_uuid(&uuid, &mut conn).await {
|
let user = match User::find_by_uuid(uuid, &mut conn).await {
|
||||||
Some(user) => user,
|
Some(user) => user,
|
||||||
None => err!("User doesn't exist"),
|
None => err!("User doesn't exist"),
|
||||||
};
|
};
|
||||||
@@ -874,18 +879,18 @@ async fn rotate_api_key(data: JsonUpcase<SecretVerificationRequest>, headers: He
|
|||||||
|
|
||||||
// This variant is deprecated: https://github.com/bitwarden/server/pull/2682
|
// This variant is deprecated: https://github.com/bitwarden/server/pull/2682
|
||||||
#[get("/devices/knowndevice/<email>/<uuid>")]
|
#[get("/devices/knowndevice/<email>/<uuid>")]
|
||||||
async fn get_known_device_from_path(email: String, uuid: String, mut conn: DbConn) -> JsonResult {
|
async fn get_known_device_from_path(email: &str, uuid: &str, mut conn: DbConn) -> JsonResult {
|
||||||
// This endpoint doesn't have auth header
|
// This endpoint doesn't have auth header
|
||||||
let mut result = false;
|
let mut result = false;
|
||||||
if let Some(user) = User::find_by_mail(&email, &mut conn).await {
|
if let Some(user) = User::find_by_mail(email, &mut conn).await {
|
||||||
result = Device::find_by_uuid_and_user(&uuid, &user.uuid, &mut conn).await.is_some();
|
result = Device::find_by_uuid_and_user(uuid, &user.uuid, &mut conn).await.is_some();
|
||||||
}
|
}
|
||||||
Ok(Json(json!(result)))
|
Ok(Json(json!(result)))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/devices/knowndevice")]
|
#[get("/devices/knowndevice")]
|
||||||
async fn get_known_device(device: KnownDevice, conn: DbConn) -> JsonResult {
|
async fn get_known_device(device: KnownDevice, conn: DbConn) -> JsonResult {
|
||||||
get_known_device_from_path(device.email, device.uuid, conn).await
|
get_known_device_from_path(&device.email, &device.uuid, conn).await
|
||||||
}
|
}
|
||||||
|
|
||||||
struct KnownDevice {
|
struct KnownDevice {
|
||||||
@@ -930,3 +935,64 @@ impl<'r> FromRequest<'r> for KnownDevice {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
struct PushToken {
|
||||||
|
PushToken: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/devices/identifier/<uuid>/token", data = "<data>")]
|
||||||
|
async fn post_device_token(uuid: &str, data: JsonUpcase<PushToken>, headers: Headers, conn: DbConn) -> EmptyResult {
|
||||||
|
put_device_token(uuid, data, headers, conn).await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[put("/devices/identifier/<uuid>/token", data = "<data>")]
|
||||||
|
async fn put_device_token(uuid: &str, data: JsonUpcase<PushToken>, headers: Headers, mut conn: DbConn) -> EmptyResult {
|
||||||
|
if !CONFIG.push_enabled() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let data = data.into_inner().data;
|
||||||
|
let token = data.PushToken;
|
||||||
|
let mut device = match Device::find_by_uuid_and_user(&headers.device.uuid, &headers.user.uuid, &mut conn).await {
|
||||||
|
Some(device) => device,
|
||||||
|
None => err!(format!("Error: device {uuid} should be present before a token can be assigned")),
|
||||||
|
};
|
||||||
|
device.push_token = Some(token);
|
||||||
|
if device.push_uuid.is_none() {
|
||||||
|
device.push_uuid = Some(uuid::Uuid::new_v4().to_string());
|
||||||
|
}
|
||||||
|
if let Err(e) = device.save(&mut conn).await {
|
||||||
|
err!(format!("An error occured while trying to save the device push token: {e}"));
|
||||||
|
}
|
||||||
|
if let Err(e) = register_push_device(headers.user.uuid, device).await {
|
||||||
|
err!(format!("An error occured while proceeding registration of a device: {e}"));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[put("/devices/identifier/<uuid>/clear-token")]
|
||||||
|
async fn put_clear_device_token(uuid: &str, mut conn: DbConn) -> EmptyResult {
|
||||||
|
// This only clears push token
|
||||||
|
// https://github.com/bitwarden/core/blob/master/src/Api/Controllers/DevicesController.cs#L109
|
||||||
|
// https://github.com/bitwarden/core/blob/master/src/Core/Services/Implementations/DeviceService.cs#L37
|
||||||
|
// This is somehow not implemented in any app, added it in case it is required
|
||||||
|
if !CONFIG.push_enabled() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(device) = Device::find_by_uuid(uuid, &mut conn).await {
|
||||||
|
Device::clear_push_token_by_uuid(uuid, &mut conn).await?;
|
||||||
|
unregister_push_device(device.uuid).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// On upstream server, both PUT and POST are declared. Implementing the POST method in case it would be useful somewhere
|
||||||
|
#[post("/devices/identifier/<uuid>/clear-token")]
|
||||||
|
async fn post_clear_device_token(uuid: &str, conn: DbConn) -> EmptyResult {
|
||||||
|
put_clear_device_token(uuid, conn).await
|
||||||
|
}
|
||||||
|
@@ -172,8 +172,8 @@ async fn get_ciphers(headers: Headers, mut conn: DbConn) -> Json<Value> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[get("/ciphers/<uuid>")]
|
#[get("/ciphers/<uuid>")]
|
||||||
async fn get_cipher(uuid: String, headers: Headers, mut conn: DbConn) -> JsonResult {
|
async fn get_cipher(uuid: &str, headers: Headers, mut conn: DbConn) -> JsonResult {
|
||||||
let cipher = match Cipher::find_by_uuid(&uuid, &mut conn).await {
|
let cipher = match Cipher::find_by_uuid(uuid, &mut conn).await {
|
||||||
Some(cipher) => cipher,
|
Some(cipher) => cipher,
|
||||||
None => err!("Cipher doesn't exist"),
|
None => err!("Cipher doesn't exist"),
|
||||||
};
|
};
|
||||||
@@ -186,13 +186,13 @@ async fn get_cipher(uuid: String, headers: Headers, mut conn: DbConn) -> JsonRes
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[get("/ciphers/<uuid>/admin")]
|
#[get("/ciphers/<uuid>/admin")]
|
||||||
async fn get_cipher_admin(uuid: String, headers: Headers, conn: DbConn) -> JsonResult {
|
async fn get_cipher_admin(uuid: &str, headers: Headers, conn: DbConn) -> JsonResult {
|
||||||
// TODO: Implement this correctly
|
// TODO: Implement this correctly
|
||||||
get_cipher(uuid, headers, conn).await
|
get_cipher(uuid, headers, conn).await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/ciphers/<uuid>/details")]
|
#[get("/ciphers/<uuid>/details")]
|
||||||
async fn get_cipher_details(uuid: String, headers: Headers, conn: DbConn) -> JsonResult {
|
async fn get_cipher_details(uuid: &str, headers: Headers, conn: DbConn) -> JsonResult {
|
||||||
get_cipher(uuid, headers, conn).await
|
get_cipher(uuid, headers, conn).await
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -210,7 +210,8 @@ pub struct CipherData {
|
|||||||
Login = 1,
|
Login = 1,
|
||||||
SecureNote = 2,
|
SecureNote = 2,
|
||||||
Card = 3,
|
Card = 3,
|
||||||
Identity = 4
|
Identity = 4,
|
||||||
|
Fido2Key = 5
|
||||||
*/
|
*/
|
||||||
pub Type: i32,
|
pub Type: i32,
|
||||||
pub Name: String,
|
pub Name: String,
|
||||||
@@ -222,6 +223,7 @@ pub struct CipherData {
|
|||||||
SecureNote: Option<Value>,
|
SecureNote: Option<Value>,
|
||||||
Card: Option<Value>,
|
Card: Option<Value>,
|
||||||
Identity: Option<Value>,
|
Identity: Option<Value>,
|
||||||
|
Fido2Key: Option<Value>,
|
||||||
|
|
||||||
Favorite: Option<bool>,
|
Favorite: Option<bool>,
|
||||||
Reprompt: Option<i32>,
|
Reprompt: Option<i32>,
|
||||||
@@ -464,6 +466,7 @@ pub async fn update_cipher_from_data(
|
|||||||
2 => data.SecureNote,
|
2 => data.SecureNote,
|
||||||
3 => data.Card,
|
3 => data.Card,
|
||||||
4 => data.Identity,
|
4 => data.Identity,
|
||||||
|
5 => data.Fido2Key,
|
||||||
_ => err!("Invalid type"),
|
_ => err!("Invalid type"),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -503,7 +506,7 @@ pub async fn update_cipher_from_data(
|
|||||||
log_event(
|
log_event(
|
||||||
event_type as i32,
|
event_type as i32,
|
||||||
&cipher.uuid,
|
&cipher.uuid,
|
||||||
String::from(org_uuid),
|
org_uuid,
|
||||||
headers.user.uuid.clone(),
|
headers.user.uuid.clone(),
|
||||||
headers.device.atype,
|
headers.device.atype,
|
||||||
&headers.ip.ip,
|
&headers.ip.ip,
|
||||||
@@ -511,10 +514,9 @@ pub async fn update_cipher_from_data(
|
|||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
nt.send_cipher_update(ut, cipher, &cipher.update_users_revision(conn).await, &headers.device.uuid, None, conn)
|
||||||
nt.send_cipher_update(ut, cipher, &cipher.update_users_revision(conn).await, &headers.device.uuid).await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -580,13 +582,14 @@ async fn post_ciphers_import(
|
|||||||
let mut user = headers.user;
|
let mut user = headers.user;
|
||||||
user.update_revision(&mut conn).await?;
|
user.update_revision(&mut conn).await?;
|
||||||
nt.send_user_update(UpdateType::SyncVault, &user).await;
|
nt.send_user_update(UpdateType::SyncVault, &user).await;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Called when an org admin modifies an existing org cipher.
|
/// Called when an org admin modifies an existing org cipher.
|
||||||
#[put("/ciphers/<uuid>/admin", data = "<data>")]
|
#[put("/ciphers/<uuid>/admin", data = "<data>")]
|
||||||
async fn put_cipher_admin(
|
async fn put_cipher_admin(
|
||||||
uuid: String,
|
uuid: &str,
|
||||||
data: JsonUpcase<CipherData>,
|
data: JsonUpcase<CipherData>,
|
||||||
headers: Headers,
|
headers: Headers,
|
||||||
conn: DbConn,
|
conn: DbConn,
|
||||||
@@ -597,7 +600,7 @@ async fn put_cipher_admin(
|
|||||||
|
|
||||||
#[post("/ciphers/<uuid>/admin", data = "<data>")]
|
#[post("/ciphers/<uuid>/admin", data = "<data>")]
|
||||||
async fn post_cipher_admin(
|
async fn post_cipher_admin(
|
||||||
uuid: String,
|
uuid: &str,
|
||||||
data: JsonUpcase<CipherData>,
|
data: JsonUpcase<CipherData>,
|
||||||
headers: Headers,
|
headers: Headers,
|
||||||
conn: DbConn,
|
conn: DbConn,
|
||||||
@@ -608,7 +611,7 @@ async fn post_cipher_admin(
|
|||||||
|
|
||||||
#[post("/ciphers/<uuid>", data = "<data>")]
|
#[post("/ciphers/<uuid>", data = "<data>")]
|
||||||
async fn post_cipher(
|
async fn post_cipher(
|
||||||
uuid: String,
|
uuid: &str,
|
||||||
data: JsonUpcase<CipherData>,
|
data: JsonUpcase<CipherData>,
|
||||||
headers: Headers,
|
headers: Headers,
|
||||||
conn: DbConn,
|
conn: DbConn,
|
||||||
@@ -619,7 +622,7 @@ async fn post_cipher(
|
|||||||
|
|
||||||
#[put("/ciphers/<uuid>", data = "<data>")]
|
#[put("/ciphers/<uuid>", data = "<data>")]
|
||||||
async fn put_cipher(
|
async fn put_cipher(
|
||||||
uuid: String,
|
uuid: &str,
|
||||||
data: JsonUpcase<CipherData>,
|
data: JsonUpcase<CipherData>,
|
||||||
headers: Headers,
|
headers: Headers,
|
||||||
mut conn: DbConn,
|
mut conn: DbConn,
|
||||||
@@ -627,7 +630,7 @@ async fn put_cipher(
|
|||||||
) -> JsonResult {
|
) -> JsonResult {
|
||||||
let data: CipherData = data.into_inner().data;
|
let data: CipherData = data.into_inner().data;
|
||||||
|
|
||||||
let mut cipher = match Cipher::find_by_uuid(&uuid, &mut conn).await {
|
let mut cipher = match Cipher::find_by_uuid(uuid, &mut conn).await {
|
||||||
Some(cipher) => cipher,
|
Some(cipher) => cipher,
|
||||||
None => err!("Cipher doesn't exist"),
|
None => err!("Cipher doesn't exist"),
|
||||||
};
|
};
|
||||||
@@ -648,7 +651,7 @@ async fn put_cipher(
|
|||||||
|
|
||||||
#[post("/ciphers/<uuid>/partial", data = "<data>")]
|
#[post("/ciphers/<uuid>/partial", data = "<data>")]
|
||||||
async fn post_cipher_partial(
|
async fn post_cipher_partial(
|
||||||
uuid: String,
|
uuid: &str,
|
||||||
data: JsonUpcase<PartialCipherData>,
|
data: JsonUpcase<PartialCipherData>,
|
||||||
headers: Headers,
|
headers: Headers,
|
||||||
conn: DbConn,
|
conn: DbConn,
|
||||||
@@ -659,14 +662,14 @@ async fn post_cipher_partial(
|
|||||||
// Only update the folder and favorite for the user, since this cipher is read-only
|
// Only update the folder and favorite for the user, since this cipher is read-only
|
||||||
#[put("/ciphers/<uuid>/partial", data = "<data>")]
|
#[put("/ciphers/<uuid>/partial", data = "<data>")]
|
||||||
async fn put_cipher_partial(
|
async fn put_cipher_partial(
|
||||||
uuid: String,
|
uuid: &str,
|
||||||
data: JsonUpcase<PartialCipherData>,
|
data: JsonUpcase<PartialCipherData>,
|
||||||
headers: Headers,
|
headers: Headers,
|
||||||
mut conn: DbConn,
|
mut conn: DbConn,
|
||||||
) -> JsonResult {
|
) -> JsonResult {
|
||||||
let data: PartialCipherData = data.into_inner().data;
|
let data: PartialCipherData = data.into_inner().data;
|
||||||
|
|
||||||
let cipher = match Cipher::find_by_uuid(&uuid, &mut conn).await {
|
let cipher = match Cipher::find_by_uuid(uuid, &mut conn).await {
|
||||||
Some(cipher) => cipher,
|
Some(cipher) => cipher,
|
||||||
None => err!("Cipher doesn't exist"),
|
None => err!("Cipher doesn't exist"),
|
||||||
};
|
};
|
||||||
@@ -698,44 +701,48 @@ struct CollectionsAdminData {
|
|||||||
|
|
||||||
#[put("/ciphers/<uuid>/collections", data = "<data>")]
|
#[put("/ciphers/<uuid>/collections", data = "<data>")]
|
||||||
async fn put_collections_update(
|
async fn put_collections_update(
|
||||||
uuid: String,
|
uuid: &str,
|
||||||
data: JsonUpcase<CollectionsAdminData>,
|
data: JsonUpcase<CollectionsAdminData>,
|
||||||
headers: Headers,
|
headers: Headers,
|
||||||
conn: DbConn,
|
conn: DbConn,
|
||||||
|
nt: Notify<'_>,
|
||||||
) -> EmptyResult {
|
) -> EmptyResult {
|
||||||
post_collections_admin(uuid, data, headers, conn).await
|
post_collections_admin(uuid, data, headers, conn, nt).await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/ciphers/<uuid>/collections", data = "<data>")]
|
#[post("/ciphers/<uuid>/collections", data = "<data>")]
|
||||||
async fn post_collections_update(
|
async fn post_collections_update(
|
||||||
uuid: String,
|
uuid: &str,
|
||||||
data: JsonUpcase<CollectionsAdminData>,
|
data: JsonUpcase<CollectionsAdminData>,
|
||||||
headers: Headers,
|
headers: Headers,
|
||||||
conn: DbConn,
|
conn: DbConn,
|
||||||
|
nt: Notify<'_>,
|
||||||
) -> EmptyResult {
|
) -> EmptyResult {
|
||||||
post_collections_admin(uuid, data, headers, conn).await
|
post_collections_admin(uuid, data, headers, conn, nt).await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[put("/ciphers/<uuid>/collections-admin", data = "<data>")]
|
#[put("/ciphers/<uuid>/collections-admin", data = "<data>")]
|
||||||
async fn put_collections_admin(
|
async fn put_collections_admin(
|
||||||
uuid: String,
|
uuid: &str,
|
||||||
data: JsonUpcase<CollectionsAdminData>,
|
data: JsonUpcase<CollectionsAdminData>,
|
||||||
headers: Headers,
|
headers: Headers,
|
||||||
conn: DbConn,
|
conn: DbConn,
|
||||||
|
nt: Notify<'_>,
|
||||||
) -> EmptyResult {
|
) -> EmptyResult {
|
||||||
post_collections_admin(uuid, data, headers, conn).await
|
post_collections_admin(uuid, data, headers, conn, nt).await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/ciphers/<uuid>/collections-admin", data = "<data>")]
|
#[post("/ciphers/<uuid>/collections-admin", data = "<data>")]
|
||||||
async fn post_collections_admin(
|
async fn post_collections_admin(
|
||||||
uuid: String,
|
uuid: &str,
|
||||||
data: JsonUpcase<CollectionsAdminData>,
|
data: JsonUpcase<CollectionsAdminData>,
|
||||||
headers: Headers,
|
headers: Headers,
|
||||||
mut conn: DbConn,
|
mut conn: DbConn,
|
||||||
|
nt: Notify<'_>,
|
||||||
) -> EmptyResult {
|
) -> EmptyResult {
|
||||||
let data: CollectionsAdminData = data.into_inner().data;
|
let data: CollectionsAdminData = data.into_inner().data;
|
||||||
|
|
||||||
let cipher = match Cipher::find_by_uuid(&uuid, &mut conn).await {
|
let cipher = match Cipher::find_by_uuid(uuid, &mut conn).await {
|
||||||
Some(cipher) => cipher,
|
Some(cipher) => cipher,
|
||||||
None => err!("Cipher doesn't exist"),
|
None => err!("Cipher doesn't exist"),
|
||||||
};
|
};
|
||||||
@@ -767,10 +774,20 @@ async fn post_collections_admin(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nt.send_cipher_update(
|
||||||
|
UpdateType::SyncCipherUpdate,
|
||||||
|
&cipher,
|
||||||
|
&cipher.update_users_revision(&mut conn).await,
|
||||||
|
&headers.device.uuid,
|
||||||
|
Some(Vec::from_iter(posted_collections)),
|
||||||
|
&mut conn,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
log_event(
|
log_event(
|
||||||
EventType::CipherUpdatedCollections as i32,
|
EventType::CipherUpdatedCollections as i32,
|
||||||
&cipher.uuid,
|
&cipher.uuid,
|
||||||
cipher.organization_uuid.unwrap(),
|
&cipher.organization_uuid.unwrap(),
|
||||||
headers.user.uuid.clone(),
|
headers.user.uuid.clone(),
|
||||||
headers.device.atype,
|
headers.device.atype,
|
||||||
&headers.ip.ip,
|
&headers.ip.ip,
|
||||||
@@ -790,7 +807,7 @@ struct ShareCipherData {
|
|||||||
|
|
||||||
#[post("/ciphers/<uuid>/share", data = "<data>")]
|
#[post("/ciphers/<uuid>/share", data = "<data>")]
|
||||||
async fn post_cipher_share(
|
async fn post_cipher_share(
|
||||||
uuid: String,
|
uuid: &str,
|
||||||
data: JsonUpcase<ShareCipherData>,
|
data: JsonUpcase<ShareCipherData>,
|
||||||
headers: Headers,
|
headers: Headers,
|
||||||
mut conn: DbConn,
|
mut conn: DbConn,
|
||||||
@@ -798,12 +815,12 @@ async fn post_cipher_share(
|
|||||||
) -> JsonResult {
|
) -> JsonResult {
|
||||||
let data: ShareCipherData = data.into_inner().data;
|
let data: ShareCipherData = data.into_inner().data;
|
||||||
|
|
||||||
share_cipher_by_uuid(&uuid, data, &headers, &mut conn, &nt).await
|
share_cipher_by_uuid(uuid, data, &headers, &mut conn, &nt).await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[put("/ciphers/<uuid>/share", data = "<data>")]
|
#[put("/ciphers/<uuid>/share", data = "<data>")]
|
||||||
async fn put_cipher_share(
|
async fn put_cipher_share(
|
||||||
uuid: String,
|
uuid: &str,
|
||||||
data: JsonUpcase<ShareCipherData>,
|
data: JsonUpcase<ShareCipherData>,
|
||||||
headers: Headers,
|
headers: Headers,
|
||||||
mut conn: DbConn,
|
mut conn: DbConn,
|
||||||
@@ -811,7 +828,7 @@ async fn put_cipher_share(
|
|||||||
) -> JsonResult {
|
) -> JsonResult {
|
||||||
let data: ShareCipherData = data.into_inner().data;
|
let data: ShareCipherData = data.into_inner().data;
|
||||||
|
|
||||||
share_cipher_by_uuid(&uuid, data, &headers, &mut conn, &nt).await
|
share_cipher_by_uuid(uuid, data, &headers, &mut conn, &nt).await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
@@ -916,8 +933,17 @@ async fn share_cipher_by_uuid(
|
|||||||
/// their object storage service. For self-hosted instances, it basically just
|
/// their object storage service. For self-hosted instances, it basically just
|
||||||
/// redirects to the same location as before the v2 API.
|
/// redirects to the same location as before the v2 API.
|
||||||
#[get("/ciphers/<uuid>/attachment/<attachment_id>")]
|
#[get("/ciphers/<uuid>/attachment/<attachment_id>")]
|
||||||
async fn get_attachment(uuid: String, attachment_id: String, headers: Headers, mut conn: DbConn) -> JsonResult {
|
async fn get_attachment(uuid: &str, attachment_id: &str, headers: Headers, mut conn: DbConn) -> JsonResult {
|
||||||
match Attachment::find_by_id(&attachment_id, &mut conn).await {
|
let cipher = match Cipher::find_by_uuid(uuid, &mut conn).await {
|
||||||
|
Some(cipher) => cipher,
|
||||||
|
None => err!("Cipher doesn't exist"),
|
||||||
|
};
|
||||||
|
|
||||||
|
if !cipher.is_accessible_to_user(&headers.user.uuid, &mut conn).await {
|
||||||
|
err!("Cipher is not accessible")
|
||||||
|
}
|
||||||
|
|
||||||
|
match Attachment::find_by_id(attachment_id, &mut conn).await {
|
||||||
Some(attachment) if uuid == attachment.cipher_uuid => Ok(Json(attachment.to_json(&headers.host))),
|
Some(attachment) if uuid == attachment.cipher_uuid => Ok(Json(attachment.to_json(&headers.host))),
|
||||||
Some(_) => err!("Attachment doesn't belong to cipher"),
|
Some(_) => err!("Attachment doesn't belong to cipher"),
|
||||||
None => err!("Attachment doesn't exist"),
|
None => err!("Attachment doesn't exist"),
|
||||||
@@ -944,12 +970,12 @@ enum FileUploadType {
|
|||||||
/// For self-hosted instances, it's another API on the local instance.
|
/// For self-hosted instances, it's another API on the local instance.
|
||||||
#[post("/ciphers/<uuid>/attachment/v2", data = "<data>")]
|
#[post("/ciphers/<uuid>/attachment/v2", data = "<data>")]
|
||||||
async fn post_attachment_v2(
|
async fn post_attachment_v2(
|
||||||
uuid: String,
|
uuid: &str,
|
||||||
data: JsonUpcase<AttachmentRequestData>,
|
data: JsonUpcase<AttachmentRequestData>,
|
||||||
headers: Headers,
|
headers: Headers,
|
||||||
mut conn: DbConn,
|
mut conn: DbConn,
|
||||||
) -> JsonResult {
|
) -> JsonResult {
|
||||||
let cipher = match Cipher::find_by_uuid(&uuid, &mut conn).await {
|
let cipher = match Cipher::find_by_uuid(uuid, &mut conn).await {
|
||||||
Some(cipher) => cipher,
|
Some(cipher) => cipher,
|
||||||
None => err!("Cipher doesn't exist"),
|
None => err!("Cipher doesn't exist"),
|
||||||
};
|
};
|
||||||
@@ -995,13 +1021,13 @@ struct UploadData<'f> {
|
|||||||
/// database record, which is passed in as `attachment`.
|
/// database record, which is passed in as `attachment`.
|
||||||
async fn save_attachment(
|
async fn save_attachment(
|
||||||
mut attachment: Option<Attachment>,
|
mut attachment: Option<Attachment>,
|
||||||
cipher_uuid: String,
|
cipher_uuid: &str,
|
||||||
data: Form<UploadData<'_>>,
|
data: Form<UploadData<'_>>,
|
||||||
headers: &Headers,
|
headers: &Headers,
|
||||||
mut conn: DbConn,
|
mut conn: DbConn,
|
||||||
nt: Notify<'_>,
|
nt: Notify<'_>,
|
||||||
) -> Result<(Cipher, DbConn), crate::error::Error> {
|
) -> Result<(Cipher, DbConn), crate::error::Error> {
|
||||||
let cipher = match Cipher::find_by_uuid(&cipher_uuid, &mut conn).await {
|
let cipher = match Cipher::find_by_uuid(cipher_uuid, &mut conn).await {
|
||||||
Some(cipher) => cipher,
|
Some(cipher) => cipher,
|
||||||
None => err!("Cipher doesn't exist"),
|
None => err!("Cipher doesn't exist"),
|
||||||
};
|
};
|
||||||
@@ -1058,7 +1084,7 @@ async fn save_attachment(
|
|||||||
None => crypto::generate_attachment_id(), // Legacy API
|
None => crypto::generate_attachment_id(), // Legacy API
|
||||||
};
|
};
|
||||||
|
|
||||||
let folder_path = tokio::fs::canonicalize(&CONFIG.attachments_folder()).await?.join(&cipher_uuid);
|
let folder_path = tokio::fs::canonicalize(&CONFIG.attachments_folder()).await?.join(cipher_uuid);
|
||||||
let file_path = folder_path.join(&file_id);
|
let file_path = folder_path.join(&file_id);
|
||||||
tokio::fs::create_dir_all(&folder_path).await?;
|
tokio::fs::create_dir_all(&folder_path).await?;
|
||||||
|
|
||||||
@@ -1094,7 +1120,8 @@ async fn save_attachment(
|
|||||||
if data.key.is_none() {
|
if data.key.is_none() {
|
||||||
err!("No attachment key provided")
|
err!("No attachment key provided")
|
||||||
}
|
}
|
||||||
let attachment = Attachment::new(file_id, cipher_uuid.clone(), encrypted_filename.unwrap(), size, data.key);
|
let attachment =
|
||||||
|
Attachment::new(file_id, String::from(cipher_uuid), encrypted_filename.unwrap(), size, data.key);
|
||||||
attachment.save(&mut conn).await.expect("Error saving attachment");
|
attachment.save(&mut conn).await.expect("Error saving attachment");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1107,6 +1134,8 @@ async fn save_attachment(
|
|||||||
&cipher,
|
&cipher,
|
||||||
&cipher.update_users_revision(&mut conn).await,
|
&cipher.update_users_revision(&mut conn).await,
|
||||||
&headers.device.uuid,
|
&headers.device.uuid,
|
||||||
|
None,
|
||||||
|
&mut conn,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
@@ -1114,7 +1143,7 @@ async fn save_attachment(
|
|||||||
log_event(
|
log_event(
|
||||||
EventType::CipherAttachmentCreated as i32,
|
EventType::CipherAttachmentCreated as i32,
|
||||||
&cipher.uuid,
|
&cipher.uuid,
|
||||||
String::from(org_uuid),
|
org_uuid,
|
||||||
headers.user.uuid.clone(),
|
headers.user.uuid.clone(),
|
||||||
headers.device.atype,
|
headers.device.atype,
|
||||||
&headers.ip.ip,
|
&headers.ip.ip,
|
||||||
@@ -1132,14 +1161,14 @@ async fn save_attachment(
|
|||||||
/// with this one.
|
/// with this one.
|
||||||
#[post("/ciphers/<uuid>/attachment/<attachment_id>", format = "multipart/form-data", data = "<data>", rank = 1)]
|
#[post("/ciphers/<uuid>/attachment/<attachment_id>", format = "multipart/form-data", data = "<data>", rank = 1)]
|
||||||
async fn post_attachment_v2_data(
|
async fn post_attachment_v2_data(
|
||||||
uuid: String,
|
uuid: &str,
|
||||||
attachment_id: String,
|
attachment_id: &str,
|
||||||
data: Form<UploadData<'_>>,
|
data: Form<UploadData<'_>>,
|
||||||
headers: Headers,
|
headers: Headers,
|
||||||
mut conn: DbConn,
|
mut conn: DbConn,
|
||||||
nt: Notify<'_>,
|
nt: Notify<'_>,
|
||||||
) -> EmptyResult {
|
) -> EmptyResult {
|
||||||
let attachment = match Attachment::find_by_id(&attachment_id, &mut conn).await {
|
let attachment = match Attachment::find_by_id(attachment_id, &mut conn).await {
|
||||||
Some(attachment) if uuid == attachment.cipher_uuid => Some(attachment),
|
Some(attachment) if uuid == attachment.cipher_uuid => Some(attachment),
|
||||||
Some(_) => err!("Attachment doesn't belong to cipher"),
|
Some(_) => err!("Attachment doesn't belong to cipher"),
|
||||||
None => err!("Attachment doesn't exist"),
|
None => err!("Attachment doesn't exist"),
|
||||||
@@ -1153,7 +1182,7 @@ async fn post_attachment_v2_data(
|
|||||||
/// Legacy API for creating an attachment associated with a cipher.
|
/// Legacy API for creating an attachment associated with a cipher.
|
||||||
#[post("/ciphers/<uuid>/attachment", format = "multipart/form-data", data = "<data>")]
|
#[post("/ciphers/<uuid>/attachment", format = "multipart/form-data", data = "<data>")]
|
||||||
async fn post_attachment(
|
async fn post_attachment(
|
||||||
uuid: String,
|
uuid: &str,
|
||||||
data: Form<UploadData<'_>>,
|
data: Form<UploadData<'_>>,
|
||||||
headers: Headers,
|
headers: Headers,
|
||||||
conn: DbConn,
|
conn: DbConn,
|
||||||
@@ -1170,7 +1199,7 @@ async fn post_attachment(
|
|||||||
|
|
||||||
#[post("/ciphers/<uuid>/attachment-admin", format = "multipart/form-data", data = "<data>")]
|
#[post("/ciphers/<uuid>/attachment-admin", format = "multipart/form-data", data = "<data>")]
|
||||||
async fn post_attachment_admin(
|
async fn post_attachment_admin(
|
||||||
uuid: String,
|
uuid: &str,
|
||||||
data: Form<UploadData<'_>>,
|
data: Form<UploadData<'_>>,
|
||||||
headers: Headers,
|
headers: Headers,
|
||||||
conn: DbConn,
|
conn: DbConn,
|
||||||
@@ -1181,21 +1210,21 @@ async fn post_attachment_admin(
|
|||||||
|
|
||||||
#[post("/ciphers/<uuid>/attachment/<attachment_id>/share", format = "multipart/form-data", data = "<data>")]
|
#[post("/ciphers/<uuid>/attachment/<attachment_id>/share", format = "multipart/form-data", data = "<data>")]
|
||||||
async fn post_attachment_share(
|
async fn post_attachment_share(
|
||||||
uuid: String,
|
uuid: &str,
|
||||||
attachment_id: String,
|
attachment_id: &str,
|
||||||
data: Form<UploadData<'_>>,
|
data: Form<UploadData<'_>>,
|
||||||
headers: Headers,
|
headers: Headers,
|
||||||
mut conn: DbConn,
|
mut conn: DbConn,
|
||||||
nt: Notify<'_>,
|
nt: Notify<'_>,
|
||||||
) -> JsonResult {
|
) -> JsonResult {
|
||||||
_delete_cipher_attachment_by_id(&uuid, &attachment_id, &headers, &mut conn, &nt).await?;
|
_delete_cipher_attachment_by_id(uuid, attachment_id, &headers, &mut conn, &nt).await?;
|
||||||
post_attachment(uuid, data, headers, conn, nt).await
|
post_attachment(uuid, data, headers, conn, nt).await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/ciphers/<uuid>/attachment/<attachment_id>/delete-admin")]
|
#[post("/ciphers/<uuid>/attachment/<attachment_id>/delete-admin")]
|
||||||
async fn delete_attachment_post_admin(
|
async fn delete_attachment_post_admin(
|
||||||
uuid: String,
|
uuid: &str,
|
||||||
attachment_id: String,
|
attachment_id: &str,
|
||||||
headers: Headers,
|
headers: Headers,
|
||||||
conn: DbConn,
|
conn: DbConn,
|
||||||
nt: Notify<'_>,
|
nt: Notify<'_>,
|
||||||
@@ -1205,8 +1234,8 @@ async fn delete_attachment_post_admin(
|
|||||||
|
|
||||||
#[post("/ciphers/<uuid>/attachment/<attachment_id>/delete")]
|
#[post("/ciphers/<uuid>/attachment/<attachment_id>/delete")]
|
||||||
async fn delete_attachment_post(
|
async fn delete_attachment_post(
|
||||||
uuid: String,
|
uuid: &str,
|
||||||
attachment_id: String,
|
attachment_id: &str,
|
||||||
headers: Headers,
|
headers: Headers,
|
||||||
conn: DbConn,
|
conn: DbConn,
|
||||||
nt: Notify<'_>,
|
nt: Notify<'_>,
|
||||||
@@ -1216,58 +1245,58 @@ async fn delete_attachment_post(
|
|||||||
|
|
||||||
#[delete("/ciphers/<uuid>/attachment/<attachment_id>")]
|
#[delete("/ciphers/<uuid>/attachment/<attachment_id>")]
|
||||||
async fn delete_attachment(
|
async fn delete_attachment(
|
||||||
uuid: String,
|
uuid: &str,
|
||||||
attachment_id: String,
|
attachment_id: &str,
|
||||||
headers: Headers,
|
headers: Headers,
|
||||||
mut conn: DbConn,
|
mut conn: DbConn,
|
||||||
nt: Notify<'_>,
|
nt: Notify<'_>,
|
||||||
) -> EmptyResult {
|
) -> EmptyResult {
|
||||||
_delete_cipher_attachment_by_id(&uuid, &attachment_id, &headers, &mut conn, &nt).await
|
_delete_cipher_attachment_by_id(uuid, attachment_id, &headers, &mut conn, &nt).await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[delete("/ciphers/<uuid>/attachment/<attachment_id>/admin")]
|
#[delete("/ciphers/<uuid>/attachment/<attachment_id>/admin")]
|
||||||
async fn delete_attachment_admin(
|
async fn delete_attachment_admin(
|
||||||
uuid: String,
|
uuid: &str,
|
||||||
attachment_id: String,
|
attachment_id: &str,
|
||||||
headers: Headers,
|
headers: Headers,
|
||||||
mut conn: DbConn,
|
mut conn: DbConn,
|
||||||
nt: Notify<'_>,
|
nt: Notify<'_>,
|
||||||
) -> EmptyResult {
|
) -> EmptyResult {
|
||||||
_delete_cipher_attachment_by_id(&uuid, &attachment_id, &headers, &mut conn, &nt).await
|
_delete_cipher_attachment_by_id(uuid, attachment_id, &headers, &mut conn, &nt).await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/ciphers/<uuid>/delete")]
|
#[post("/ciphers/<uuid>/delete")]
|
||||||
async fn delete_cipher_post(uuid: String, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult {
|
async fn delete_cipher_post(uuid: &str, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult {
|
||||||
_delete_cipher_by_uuid(&uuid, &headers, &mut conn, false, &nt).await
|
_delete_cipher_by_uuid(uuid, &headers, &mut conn, false, &nt).await
|
||||||
// permanent delete
|
// permanent delete
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/ciphers/<uuid>/delete-admin")]
|
#[post("/ciphers/<uuid>/delete-admin")]
|
||||||
async fn delete_cipher_post_admin(uuid: String, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult {
|
async fn delete_cipher_post_admin(uuid: &str, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult {
|
||||||
_delete_cipher_by_uuid(&uuid, &headers, &mut conn, false, &nt).await
|
_delete_cipher_by_uuid(uuid, &headers, &mut conn, false, &nt).await
|
||||||
// permanent delete
|
// permanent delete
|
||||||
}
|
}
|
||||||
|
|
||||||
#[put("/ciphers/<uuid>/delete")]
|
#[put("/ciphers/<uuid>/delete")]
|
||||||
async fn delete_cipher_put(uuid: String, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult {
|
async fn delete_cipher_put(uuid: &str, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult {
|
||||||
_delete_cipher_by_uuid(&uuid, &headers, &mut conn, true, &nt).await
|
_delete_cipher_by_uuid(uuid, &headers, &mut conn, true, &nt).await
|
||||||
// soft delete
|
// soft delete
|
||||||
}
|
}
|
||||||
|
|
||||||
#[put("/ciphers/<uuid>/delete-admin")]
|
#[put("/ciphers/<uuid>/delete-admin")]
|
||||||
async fn delete_cipher_put_admin(uuid: String, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult {
|
async fn delete_cipher_put_admin(uuid: &str, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult {
|
||||||
_delete_cipher_by_uuid(&uuid, &headers, &mut conn, true, &nt).await
|
_delete_cipher_by_uuid(uuid, &headers, &mut conn, true, &nt).await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[delete("/ciphers/<uuid>")]
|
#[delete("/ciphers/<uuid>")]
|
||||||
async fn delete_cipher(uuid: String, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult {
|
async fn delete_cipher(uuid: &str, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult {
|
||||||
_delete_cipher_by_uuid(&uuid, &headers, &mut conn, false, &nt).await
|
_delete_cipher_by_uuid(uuid, &headers, &mut conn, false, &nt).await
|
||||||
// permanent delete
|
// permanent delete
|
||||||
}
|
}
|
||||||
|
|
||||||
#[delete("/ciphers/<uuid>/admin")]
|
#[delete("/ciphers/<uuid>/admin")]
|
||||||
async fn delete_cipher_admin(uuid: String, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult {
|
async fn delete_cipher_admin(uuid: &str, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult {
|
||||||
_delete_cipher_by_uuid(&uuid, &headers, &mut conn, false, &nt).await
|
_delete_cipher_by_uuid(uuid, &headers, &mut conn, false, &nt).await
|
||||||
// permanent delete
|
// permanent delete
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1332,13 +1361,13 @@ async fn delete_cipher_selected_put_admin(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[put("/ciphers/<uuid>/restore")]
|
#[put("/ciphers/<uuid>/restore")]
|
||||||
async fn restore_cipher_put(uuid: String, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> JsonResult {
|
async fn restore_cipher_put(uuid: &str, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> JsonResult {
|
||||||
_restore_cipher_by_uuid(&uuid, &headers, &mut conn, &nt).await
|
_restore_cipher_by_uuid(uuid, &headers, &mut conn, &nt).await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[put("/ciphers/<uuid>/restore-admin")]
|
#[put("/ciphers/<uuid>/restore-admin")]
|
||||||
async fn restore_cipher_put_admin(uuid: String, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> JsonResult {
|
async fn restore_cipher_put_admin(uuid: &str, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> JsonResult {
|
||||||
_restore_cipher_by_uuid(&uuid, &headers, &mut conn, &nt).await
|
_restore_cipher_by_uuid(uuid, &headers, &mut conn, &nt).await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[put("/ciphers/restore", data = "<data>")]
|
#[put("/ciphers/restore", data = "<data>")]
|
||||||
@@ -1392,7 +1421,15 @@ async fn move_cipher_selected(
|
|||||||
// Move cipher
|
// Move cipher
|
||||||
cipher.move_to_folder(data.FolderId.clone(), &user_uuid, &mut conn).await?;
|
cipher.move_to_folder(data.FolderId.clone(), &user_uuid, &mut conn).await?;
|
||||||
|
|
||||||
nt.send_cipher_update(UpdateType::SyncCipherUpdate, &cipher, &[user_uuid.clone()], &headers.device.uuid).await;
|
nt.send_cipher_update(
|
||||||
|
UpdateType::SyncCipherUpdate,
|
||||||
|
&cipher,
|
||||||
|
&[user_uuid.clone()],
|
||||||
|
&headers.device.uuid,
|
||||||
|
None,
|
||||||
|
&mut conn,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -1444,7 +1481,7 @@ async fn delete_all(
|
|||||||
log_event(
|
log_event(
|
||||||
EventType::OrganizationPurgedVault as i32,
|
EventType::OrganizationPurgedVault as i32,
|
||||||
&org_data.org_id,
|
&org_data.org_id,
|
||||||
org_data.org_id.clone(),
|
&org_data.org_id,
|
||||||
user.uuid,
|
user.uuid,
|
||||||
headers.device.atype,
|
headers.device.atype,
|
||||||
&headers.ip.ip,
|
&headers.ip.ip,
|
||||||
@@ -1473,6 +1510,7 @@ async fn delete_all(
|
|||||||
|
|
||||||
user.update_revision(&mut conn).await?;
|
user.update_revision(&mut conn).await?;
|
||||||
nt.send_user_update(UpdateType::SyncVault, &user).await;
|
nt.send_user_update(UpdateType::SyncVault, &user).await;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1502,6 +1540,8 @@ async fn _delete_cipher_by_uuid(
|
|||||||
&cipher,
|
&cipher,
|
||||||
&cipher.update_users_revision(conn).await,
|
&cipher.update_users_revision(conn).await,
|
||||||
&headers.device.uuid,
|
&headers.device.uuid,
|
||||||
|
None,
|
||||||
|
conn,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
} else {
|
} else {
|
||||||
@@ -1511,6 +1551,8 @@ async fn _delete_cipher_by_uuid(
|
|||||||
&cipher,
|
&cipher,
|
||||||
&cipher.update_users_revision(conn).await,
|
&cipher.update_users_revision(conn).await,
|
||||||
&headers.device.uuid,
|
&headers.device.uuid,
|
||||||
|
None,
|
||||||
|
conn,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
@@ -1524,7 +1566,7 @@ async fn _delete_cipher_by_uuid(
|
|||||||
log_event(
|
log_event(
|
||||||
event_type,
|
event_type,
|
||||||
&cipher.uuid,
|
&cipher.uuid,
|
||||||
org_uuid,
|
&org_uuid,
|
||||||
headers.user.uuid.clone(),
|
headers.user.uuid.clone(),
|
||||||
headers.device.atype,
|
headers.device.atype,
|
||||||
&headers.ip.ip,
|
&headers.ip.ip,
|
||||||
@@ -1580,13 +1622,16 @@ async fn _restore_cipher_by_uuid(uuid: &str, headers: &Headers, conn: &mut DbCon
|
|||||||
&cipher,
|
&cipher,
|
||||||
&cipher.update_users_revision(conn).await,
|
&cipher.update_users_revision(conn).await,
|
||||||
&headers.device.uuid,
|
&headers.device.uuid,
|
||||||
|
None,
|
||||||
|
conn,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
if let Some(org_uuid) = &cipher.organization_uuid {
|
if let Some(org_uuid) = &cipher.organization_uuid {
|
||||||
log_event(
|
log_event(
|
||||||
EventType::CipherRestored as i32,
|
EventType::CipherRestored as i32,
|
||||||
&cipher.uuid.clone(),
|
&cipher.uuid.clone(),
|
||||||
String::from(org_uuid),
|
org_uuid,
|
||||||
headers.user.uuid.clone(),
|
headers.user.uuid.clone(),
|
||||||
headers.device.atype,
|
headers.device.atype,
|
||||||
&headers.ip.ip,
|
&headers.ip.ip,
|
||||||
@@ -1661,13 +1706,16 @@ async fn _delete_cipher_attachment_by_id(
|
|||||||
&cipher,
|
&cipher,
|
||||||
&cipher.update_users_revision(conn).await,
|
&cipher.update_users_revision(conn).await,
|
||||||
&headers.device.uuid,
|
&headers.device.uuid,
|
||||||
|
None,
|
||||||
|
conn,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
if let Some(org_uuid) = cipher.organization_uuid {
|
if let Some(org_uuid) = cipher.organization_uuid {
|
||||||
log_event(
|
log_event(
|
||||||
EventType::CipherAttachmentDeleted as i32,
|
EventType::CipherAttachmentDeleted as i32,
|
||||||
&cipher.uuid,
|
&cipher.uuid,
|
||||||
org_uuid,
|
&org_uuid,
|
||||||
headers.user.uuid.clone(),
|
headers.user.uuid.clone(),
|
||||||
headers.device.atype,
|
headers.device.atype,
|
||||||
&headers.ip.ip,
|
&headers.ip.ip,
|
||||||
|
@@ -71,10 +71,10 @@ async fn get_grantees(headers: Headers, mut conn: DbConn) -> JsonResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[get("/emergency-access/<emer_id>")]
|
#[get("/emergency-access/<emer_id>")]
|
||||||
async fn get_emergency_access(emer_id: String, mut conn: DbConn) -> JsonResult {
|
async fn get_emergency_access(emer_id: &str, mut conn: DbConn) -> JsonResult {
|
||||||
check_emergency_access_allowed()?;
|
check_emergency_access_allowed()?;
|
||||||
|
|
||||||
match EmergencyAccess::find_by_uuid(&emer_id, &mut conn).await {
|
match EmergencyAccess::find_by_uuid(emer_id, &mut conn).await {
|
||||||
Some(emergency_access) => Ok(Json(emergency_access.to_json_grantee_details(&mut conn).await)),
|
Some(emergency_access) => Ok(Json(emergency_access.to_json_grantee_details(&mut conn).await)),
|
||||||
None => err!("Emergency access not valid."),
|
None => err!("Emergency access not valid."),
|
||||||
}
|
}
|
||||||
@@ -93,17 +93,13 @@ struct EmergencyAccessUpdateData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[put("/emergency-access/<emer_id>", data = "<data>")]
|
#[put("/emergency-access/<emer_id>", data = "<data>")]
|
||||||
async fn put_emergency_access(
|
async fn put_emergency_access(emer_id: &str, data: JsonUpcase<EmergencyAccessUpdateData>, conn: DbConn) -> JsonResult {
|
||||||
emer_id: String,
|
|
||||||
data: JsonUpcase<EmergencyAccessUpdateData>,
|
|
||||||
conn: DbConn,
|
|
||||||
) -> JsonResult {
|
|
||||||
post_emergency_access(emer_id, data, conn).await
|
post_emergency_access(emer_id, data, conn).await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/emergency-access/<emer_id>", data = "<data>")]
|
#[post("/emergency-access/<emer_id>", data = "<data>")]
|
||||||
async fn post_emergency_access(
|
async fn post_emergency_access(
|
||||||
emer_id: String,
|
emer_id: &str,
|
||||||
data: JsonUpcase<EmergencyAccessUpdateData>,
|
data: JsonUpcase<EmergencyAccessUpdateData>,
|
||||||
mut conn: DbConn,
|
mut conn: DbConn,
|
||||||
) -> JsonResult {
|
) -> JsonResult {
|
||||||
@@ -111,7 +107,7 @@ async fn post_emergency_access(
|
|||||||
|
|
||||||
let data: EmergencyAccessUpdateData = data.into_inner().data;
|
let data: EmergencyAccessUpdateData = data.into_inner().data;
|
||||||
|
|
||||||
let mut emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &mut conn).await {
|
let mut emergency_access = match EmergencyAccess::find_by_uuid(emer_id, &mut conn).await {
|
||||||
Some(emergency_access) => emergency_access,
|
Some(emergency_access) => emergency_access,
|
||||||
None => err!("Emergency access not valid."),
|
None => err!("Emergency access not valid."),
|
||||||
};
|
};
|
||||||
@@ -136,12 +132,12 @@ async fn post_emergency_access(
|
|||||||
// region delete
|
// region delete
|
||||||
|
|
||||||
#[delete("/emergency-access/<emer_id>")]
|
#[delete("/emergency-access/<emer_id>")]
|
||||||
async fn delete_emergency_access(emer_id: String, headers: Headers, mut conn: DbConn) -> EmptyResult {
|
async fn delete_emergency_access(emer_id: &str, headers: Headers, mut conn: DbConn) -> EmptyResult {
|
||||||
check_emergency_access_allowed()?;
|
check_emergency_access_allowed()?;
|
||||||
|
|
||||||
let grantor_user = headers.user;
|
let grantor_user = headers.user;
|
||||||
|
|
||||||
let emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &mut conn).await {
|
let emergency_access = match EmergencyAccess::find_by_uuid(emer_id, &mut conn).await {
|
||||||
Some(emer) => {
|
Some(emer) => {
|
||||||
if emer.grantor_uuid != grantor_user.uuid && emer.grantee_uuid != Some(grantor_user.uuid) {
|
if emer.grantor_uuid != grantor_user.uuid && emer.grantee_uuid != Some(grantor_user.uuid) {
|
||||||
err!("Emergency access not valid.")
|
err!("Emergency access not valid.")
|
||||||
@@ -155,7 +151,7 @@ async fn delete_emergency_access(emer_id: String, headers: Headers, mut conn: Db
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[post("/emergency-access/<emer_id>/delete")]
|
#[post("/emergency-access/<emer_id>/delete")]
|
||||||
async fn post_delete_emergency_access(emer_id: String, headers: Headers, conn: DbConn) -> EmptyResult {
|
async fn post_delete_emergency_access(emer_id: &str, headers: Headers, conn: DbConn) -> EmptyResult {
|
||||||
delete_emergency_access(emer_id, headers, conn).await
|
delete_emergency_access(emer_id, headers, conn).await
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -243,7 +239,7 @@ async fn send_invite(data: JsonUpcase<EmergencyAccessInviteData>, headers: Heade
|
|||||||
} else {
|
} else {
|
||||||
// Automatically mark user as accepted if no email invites
|
// Automatically mark user as accepted if no email invites
|
||||||
match User::find_by_mail(&email, &mut conn).await {
|
match User::find_by_mail(&email, &mut conn).await {
|
||||||
Some(user) => match accept_invite_process(user.uuid, &mut new_emergency_access, &email, &mut conn).await {
|
Some(user) => match accept_invite_process(&user.uuid, &mut new_emergency_access, &email, &mut conn).await {
|
||||||
Ok(v) => v,
|
Ok(v) => v,
|
||||||
Err(e) => err!(e.to_string()),
|
Err(e) => err!(e.to_string()),
|
||||||
},
|
},
|
||||||
@@ -255,10 +251,10 @@ async fn send_invite(data: JsonUpcase<EmergencyAccessInviteData>, headers: Heade
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[post("/emergency-access/<emer_id>/reinvite")]
|
#[post("/emergency-access/<emer_id>/reinvite")]
|
||||||
async fn resend_invite(emer_id: String, headers: Headers, mut conn: DbConn) -> EmptyResult {
|
async fn resend_invite(emer_id: &str, headers: Headers, mut conn: DbConn) -> EmptyResult {
|
||||||
check_emergency_access_allowed()?;
|
check_emergency_access_allowed()?;
|
||||||
|
|
||||||
let mut emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &mut conn).await {
|
let mut emergency_access = match EmergencyAccess::find_by_uuid(emer_id, &mut conn).await {
|
||||||
Some(emer) => emer,
|
Some(emer) => emer,
|
||||||
None => err!("Emergency access not valid."),
|
None => err!("Emergency access not valid."),
|
||||||
};
|
};
|
||||||
@@ -299,7 +295,7 @@ async fn resend_invite(emer_id: String, headers: Headers, mut conn: DbConn) -> E
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Automatically mark user as accepted if no email invites
|
// Automatically mark user as accepted if no email invites
|
||||||
match accept_invite_process(grantee_user.uuid, &mut emergency_access, &email, &mut conn).await {
|
match accept_invite_process(&grantee_user.uuid, &mut emergency_access, &email, &mut conn).await {
|
||||||
Ok(v) => v,
|
Ok(v) => v,
|
||||||
Err(e) => err!(e.to_string()),
|
Err(e) => err!(e.to_string()),
|
||||||
}
|
}
|
||||||
@@ -315,12 +311,7 @@ struct AcceptData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[post("/emergency-access/<emer_id>/accept", data = "<data>")]
|
#[post("/emergency-access/<emer_id>/accept", data = "<data>")]
|
||||||
async fn accept_invite(
|
async fn accept_invite(emer_id: &str, data: JsonUpcase<AcceptData>, headers: Headers, mut conn: DbConn) -> EmptyResult {
|
||||||
emer_id: String,
|
|
||||||
data: JsonUpcase<AcceptData>,
|
|
||||||
headers: Headers,
|
|
||||||
mut conn: DbConn,
|
|
||||||
) -> EmptyResult {
|
|
||||||
check_emergency_access_allowed()?;
|
check_emergency_access_allowed()?;
|
||||||
|
|
||||||
let data: AcceptData = data.into_inner().data;
|
let data: AcceptData = data.into_inner().data;
|
||||||
@@ -341,7 +332,7 @@ async fn accept_invite(
|
|||||||
None => err!("Invited user not found"),
|
None => err!("Invited user not found"),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &mut conn).await {
|
let mut emergency_access = match EmergencyAccess::find_by_uuid(emer_id, &mut conn).await {
|
||||||
Some(emer) => emer,
|
Some(emer) => emer,
|
||||||
None => err!("Emergency access not valid."),
|
None => err!("Emergency access not valid."),
|
||||||
};
|
};
|
||||||
@@ -356,7 +347,7 @@ async fn accept_invite(
|
|||||||
&& grantor_user.name == claims.grantor_name
|
&& grantor_user.name == claims.grantor_name
|
||||||
&& grantor_user.email == claims.grantor_email
|
&& grantor_user.email == claims.grantor_email
|
||||||
{
|
{
|
||||||
match accept_invite_process(grantee_user.uuid, &mut emergency_access, &grantee_user.email, &mut conn).await {
|
match accept_invite_process(&grantee_user.uuid, &mut emergency_access, &grantee_user.email, &mut conn).await {
|
||||||
Ok(v) => v,
|
Ok(v) => v,
|
||||||
Err(e) => err!(e.to_string()),
|
Err(e) => err!(e.to_string()),
|
||||||
}
|
}
|
||||||
@@ -372,7 +363,7 @@ async fn accept_invite(
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn accept_invite_process(
|
async fn accept_invite_process(
|
||||||
grantee_uuid: String,
|
grantee_uuid: &str,
|
||||||
emergency_access: &mut EmergencyAccess,
|
emergency_access: &mut EmergencyAccess,
|
||||||
grantee_email: &str,
|
grantee_email: &str,
|
||||||
conn: &mut DbConn,
|
conn: &mut DbConn,
|
||||||
@@ -386,7 +377,7 @@ async fn accept_invite_process(
|
|||||||
}
|
}
|
||||||
|
|
||||||
emergency_access.status = EmergencyAccessStatus::Accepted as i32;
|
emergency_access.status = EmergencyAccessStatus::Accepted as i32;
|
||||||
emergency_access.grantee_uuid = Some(grantee_uuid);
|
emergency_access.grantee_uuid = Some(String::from(grantee_uuid));
|
||||||
emergency_access.email = None;
|
emergency_access.email = None;
|
||||||
emergency_access.save(conn).await
|
emergency_access.save(conn).await
|
||||||
}
|
}
|
||||||
@@ -399,7 +390,7 @@ struct ConfirmData {
|
|||||||
|
|
||||||
#[post("/emergency-access/<emer_id>/confirm", data = "<data>")]
|
#[post("/emergency-access/<emer_id>/confirm", data = "<data>")]
|
||||||
async fn confirm_emergency_access(
|
async fn confirm_emergency_access(
|
||||||
emer_id: String,
|
emer_id: &str,
|
||||||
data: JsonUpcase<ConfirmData>,
|
data: JsonUpcase<ConfirmData>,
|
||||||
headers: Headers,
|
headers: Headers,
|
||||||
mut conn: DbConn,
|
mut conn: DbConn,
|
||||||
@@ -410,7 +401,7 @@ async fn confirm_emergency_access(
|
|||||||
let data: ConfirmData = data.into_inner().data;
|
let data: ConfirmData = data.into_inner().data;
|
||||||
let key = data.Key;
|
let key = data.Key;
|
||||||
|
|
||||||
let mut emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &mut conn).await {
|
let mut emergency_access = match EmergencyAccess::find_by_uuid(emer_id, &mut conn).await {
|
||||||
Some(emer) => emer,
|
Some(emer) => emer,
|
||||||
None => err!("Emergency access not valid."),
|
None => err!("Emergency access not valid."),
|
||||||
};
|
};
|
||||||
@@ -452,11 +443,11 @@ async fn confirm_emergency_access(
|
|||||||
// region access emergency access
|
// region access emergency access
|
||||||
|
|
||||||
#[post("/emergency-access/<emer_id>/initiate")]
|
#[post("/emergency-access/<emer_id>/initiate")]
|
||||||
async fn initiate_emergency_access(emer_id: String, headers: Headers, mut conn: DbConn) -> JsonResult {
|
async fn initiate_emergency_access(emer_id: &str, headers: Headers, mut conn: DbConn) -> JsonResult {
|
||||||
check_emergency_access_allowed()?;
|
check_emergency_access_allowed()?;
|
||||||
|
|
||||||
let initiating_user = headers.user;
|
let initiating_user = headers.user;
|
||||||
let mut emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &mut conn).await {
|
let mut emergency_access = match EmergencyAccess::find_by_uuid(emer_id, &mut conn).await {
|
||||||
Some(emer) => emer,
|
Some(emer) => emer,
|
||||||
None => err!("Emergency access not valid."),
|
None => err!("Emergency access not valid."),
|
||||||
};
|
};
|
||||||
@@ -492,10 +483,10 @@ async fn initiate_emergency_access(emer_id: String, headers: Headers, mut conn:
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[post("/emergency-access/<emer_id>/approve")]
|
#[post("/emergency-access/<emer_id>/approve")]
|
||||||
async fn approve_emergency_access(emer_id: String, headers: Headers, mut conn: DbConn) -> JsonResult {
|
async fn approve_emergency_access(emer_id: &str, headers: Headers, mut conn: DbConn) -> JsonResult {
|
||||||
check_emergency_access_allowed()?;
|
check_emergency_access_allowed()?;
|
||||||
|
|
||||||
let mut emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &mut conn).await {
|
let mut emergency_access = match EmergencyAccess::find_by_uuid(emer_id, &mut conn).await {
|
||||||
Some(emer) => emer,
|
Some(emer) => emer,
|
||||||
None => err!("Emergency access not valid."),
|
None => err!("Emergency access not valid."),
|
||||||
};
|
};
|
||||||
@@ -530,10 +521,10 @@ async fn approve_emergency_access(emer_id: String, headers: Headers, mut conn: D
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[post("/emergency-access/<emer_id>/reject")]
|
#[post("/emergency-access/<emer_id>/reject")]
|
||||||
async fn reject_emergency_access(emer_id: String, headers: Headers, mut conn: DbConn) -> JsonResult {
|
async fn reject_emergency_access(emer_id: &str, headers: Headers, mut conn: DbConn) -> JsonResult {
|
||||||
check_emergency_access_allowed()?;
|
check_emergency_access_allowed()?;
|
||||||
|
|
||||||
let mut emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &mut conn).await {
|
let mut emergency_access = match EmergencyAccess::find_by_uuid(emer_id, &mut conn).await {
|
||||||
Some(emer) => emer,
|
Some(emer) => emer,
|
||||||
None => err!("Emergency access not valid."),
|
None => err!("Emergency access not valid."),
|
||||||
};
|
};
|
||||||
@@ -573,15 +564,15 @@ async fn reject_emergency_access(emer_id: String, headers: Headers, mut conn: Db
|
|||||||
// region action
|
// region action
|
||||||
|
|
||||||
#[post("/emergency-access/<emer_id>/view")]
|
#[post("/emergency-access/<emer_id>/view")]
|
||||||
async fn view_emergency_access(emer_id: String, headers: Headers, mut conn: DbConn) -> JsonResult {
|
async fn view_emergency_access(emer_id: &str, headers: Headers, mut conn: DbConn) -> JsonResult {
|
||||||
check_emergency_access_allowed()?;
|
check_emergency_access_allowed()?;
|
||||||
|
|
||||||
let emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &mut conn).await {
|
let emergency_access = match EmergencyAccess::find_by_uuid(emer_id, &mut conn).await {
|
||||||
Some(emer) => emer,
|
Some(emer) => emer,
|
||||||
None => err!("Emergency access not valid."),
|
None => err!("Emergency access not valid."),
|
||||||
};
|
};
|
||||||
|
|
||||||
if !is_valid_request(&emergency_access, headers.user.uuid, EmergencyAccessType::View) {
|
if !is_valid_request(&emergency_access, &headers.user.uuid, EmergencyAccessType::View) {
|
||||||
err!("Emergency access not valid.")
|
err!("Emergency access not valid.")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -610,16 +601,16 @@ async fn view_emergency_access(emer_id: String, headers: Headers, mut conn: DbCo
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[post("/emergency-access/<emer_id>/takeover")]
|
#[post("/emergency-access/<emer_id>/takeover")]
|
||||||
async fn takeover_emergency_access(emer_id: String, headers: Headers, mut conn: DbConn) -> JsonResult {
|
async fn takeover_emergency_access(emer_id: &str, headers: Headers, mut conn: DbConn) -> JsonResult {
|
||||||
check_emergency_access_allowed()?;
|
check_emergency_access_allowed()?;
|
||||||
|
|
||||||
let requesting_user = headers.user;
|
let requesting_user = headers.user;
|
||||||
let emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &mut conn).await {
|
let emergency_access = match EmergencyAccess::find_by_uuid(emer_id, &mut conn).await {
|
||||||
Some(emer) => emer,
|
Some(emer) => emer,
|
||||||
None => err!("Emergency access not valid."),
|
None => err!("Emergency access not valid."),
|
||||||
};
|
};
|
||||||
|
|
||||||
if !is_valid_request(&emergency_access, requesting_user.uuid, EmergencyAccessType::Takeover) {
|
if !is_valid_request(&emergency_access, &requesting_user.uuid, EmergencyAccessType::Takeover) {
|
||||||
err!("Emergency access not valid.")
|
err!("Emergency access not valid.")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -649,7 +640,7 @@ struct EmergencyAccessPasswordData {
|
|||||||
|
|
||||||
#[post("/emergency-access/<emer_id>/password", data = "<data>")]
|
#[post("/emergency-access/<emer_id>/password", data = "<data>")]
|
||||||
async fn password_emergency_access(
|
async fn password_emergency_access(
|
||||||
emer_id: String,
|
emer_id: &str,
|
||||||
data: JsonUpcase<EmergencyAccessPasswordData>,
|
data: JsonUpcase<EmergencyAccessPasswordData>,
|
||||||
headers: Headers,
|
headers: Headers,
|
||||||
mut conn: DbConn,
|
mut conn: DbConn,
|
||||||
@@ -661,12 +652,12 @@ async fn password_emergency_access(
|
|||||||
//let key = &data.Key;
|
//let key = &data.Key;
|
||||||
|
|
||||||
let requesting_user = headers.user;
|
let requesting_user = headers.user;
|
||||||
let emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &mut conn).await {
|
let emergency_access = match EmergencyAccess::find_by_uuid(emer_id, &mut conn).await {
|
||||||
Some(emer) => emer,
|
Some(emer) => emer,
|
||||||
None => err!("Emergency access not valid."),
|
None => err!("Emergency access not valid."),
|
||||||
};
|
};
|
||||||
|
|
||||||
if !is_valid_request(&emergency_access, requesting_user.uuid, EmergencyAccessType::Takeover) {
|
if !is_valid_request(&emergency_access, &requesting_user.uuid, EmergencyAccessType::Takeover) {
|
||||||
err!("Emergency access not valid.")
|
err!("Emergency access not valid.")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -694,14 +685,14 @@ async fn password_emergency_access(
|
|||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
#[get("/emergency-access/<emer_id>/policies")]
|
#[get("/emergency-access/<emer_id>/policies")]
|
||||||
async fn policies_emergency_access(emer_id: String, headers: Headers, mut conn: DbConn) -> JsonResult {
|
async fn policies_emergency_access(emer_id: &str, headers: Headers, mut conn: DbConn) -> JsonResult {
|
||||||
let requesting_user = headers.user;
|
let requesting_user = headers.user;
|
||||||
let emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &mut conn).await {
|
let emergency_access = match EmergencyAccess::find_by_uuid(emer_id, &mut conn).await {
|
||||||
Some(emer) => emer,
|
Some(emer) => emer,
|
||||||
None => err!("Emergency access not valid."),
|
None => err!("Emergency access not valid."),
|
||||||
};
|
};
|
||||||
|
|
||||||
if !is_valid_request(&emergency_access, requesting_user.uuid, EmergencyAccessType::Takeover) {
|
if !is_valid_request(&emergency_access, &requesting_user.uuid, EmergencyAccessType::Takeover) {
|
||||||
err!("Emergency access not valid.")
|
err!("Emergency access not valid.")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -722,10 +713,11 @@ async fn policies_emergency_access(emer_id: String, headers: Headers, mut conn:
|
|||||||
|
|
||||||
fn is_valid_request(
|
fn is_valid_request(
|
||||||
emergency_access: &EmergencyAccess,
|
emergency_access: &EmergencyAccess,
|
||||||
requesting_user_uuid: String,
|
requesting_user_uuid: &str,
|
||||||
requested_access_type: EmergencyAccessType,
|
requested_access_type: EmergencyAccessType,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
emergency_access.grantee_uuid == Some(requesting_user_uuid)
|
emergency_access.grantee_uuid.is_some()
|
||||||
|
&& emergency_access.grantee_uuid.as_ref().unwrap() == requesting_user_uuid
|
||||||
&& emergency_access.status == EmergencyAccessStatus::RecoveryApproved as i32
|
&& emergency_access.status == EmergencyAccessStatus::RecoveryApproved as i32
|
||||||
&& emergency_access.atype == requested_access_type as i32
|
&& emergency_access.atype == requested_access_type as i32
|
||||||
}
|
}
|
||||||
|
@@ -32,7 +32,7 @@ struct EventRange {
|
|||||||
|
|
||||||
// Upstream: https://github.com/bitwarden/server/blob/9ecf69d9cabce732cf2c57976dd9afa5728578fb/src/Api/Controllers/EventsController.cs#LL84C35-L84C41
|
// Upstream: https://github.com/bitwarden/server/blob/9ecf69d9cabce732cf2c57976dd9afa5728578fb/src/Api/Controllers/EventsController.cs#LL84C35-L84C41
|
||||||
#[get("/organizations/<org_id>/events?<data..>")]
|
#[get("/organizations/<org_id>/events?<data..>")]
|
||||||
async fn get_org_events(org_id: String, data: EventRange, _headers: AdminHeaders, mut conn: DbConn) -> JsonResult {
|
async fn get_org_events(org_id: &str, data: EventRange, _headers: AdminHeaders, mut conn: DbConn) -> JsonResult {
|
||||||
// Return an empty vec when we org events are disabled.
|
// Return an empty vec when we org events are disabled.
|
||||||
// This prevents client errors
|
// This prevents client errors
|
||||||
let events_json: Vec<Value> = if !CONFIG.org_events_enabled() {
|
let events_json: Vec<Value> = if !CONFIG.org_events_enabled() {
|
||||||
@@ -45,7 +45,7 @@ async fn get_org_events(org_id: String, data: EventRange, _headers: AdminHeaders
|
|||||||
parse_date(&data.end)
|
parse_date(&data.end)
|
||||||
};
|
};
|
||||||
|
|
||||||
Event::find_by_organization_uuid(&org_id, &start_date, &end_date, &mut conn)
|
Event::find_by_organization_uuid(org_id, &start_date, &end_date, &mut conn)
|
||||||
.await
|
.await
|
||||||
.iter()
|
.iter()
|
||||||
.map(|e| e.to_json())
|
.map(|e| e.to_json())
|
||||||
@@ -60,14 +60,14 @@ async fn get_org_events(org_id: String, data: EventRange, _headers: AdminHeaders
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[get("/ciphers/<cipher_id>/events?<data..>")]
|
#[get("/ciphers/<cipher_id>/events?<data..>")]
|
||||||
async fn get_cipher_events(cipher_id: String, data: EventRange, headers: Headers, mut conn: DbConn) -> JsonResult {
|
async fn get_cipher_events(cipher_id: &str, data: EventRange, headers: Headers, mut conn: DbConn) -> JsonResult {
|
||||||
// Return an empty vec when we org events are disabled.
|
// Return an empty vec when we org events are disabled.
|
||||||
// This prevents client errors
|
// This prevents client errors
|
||||||
let events_json: Vec<Value> = if !CONFIG.org_events_enabled() {
|
let events_json: Vec<Value> = if !CONFIG.org_events_enabled() {
|
||||||
Vec::with_capacity(0)
|
Vec::with_capacity(0)
|
||||||
} else {
|
} else {
|
||||||
let mut events_json = Vec::with_capacity(0);
|
let mut events_json = Vec::with_capacity(0);
|
||||||
if UserOrganization::user_has_ge_admin_access_to_cipher(&headers.user.uuid, &cipher_id, &mut conn).await {
|
if UserOrganization::user_has_ge_admin_access_to_cipher(&headers.user.uuid, cipher_id, &mut conn).await {
|
||||||
let start_date = parse_date(&data.start);
|
let start_date = parse_date(&data.start);
|
||||||
let end_date = if let Some(before_date) = &data.continuation_token {
|
let end_date = if let Some(before_date) = &data.continuation_token {
|
||||||
parse_date(before_date)
|
parse_date(before_date)
|
||||||
@@ -75,7 +75,7 @@ async fn get_cipher_events(cipher_id: String, data: EventRange, headers: Headers
|
|||||||
parse_date(&data.end)
|
parse_date(&data.end)
|
||||||
};
|
};
|
||||||
|
|
||||||
events_json = Event::find_by_cipher_uuid(&cipher_id, &start_date, &end_date, &mut conn)
|
events_json = Event::find_by_cipher_uuid(cipher_id, &start_date, &end_date, &mut conn)
|
||||||
.await
|
.await
|
||||||
.iter()
|
.iter()
|
||||||
.map(|e| e.to_json())
|
.map(|e| e.to_json())
|
||||||
@@ -93,8 +93,8 @@ async fn get_cipher_events(cipher_id: String, data: EventRange, headers: Headers
|
|||||||
|
|
||||||
#[get("/organizations/<org_id>/users/<user_org_id>/events?<data..>")]
|
#[get("/organizations/<org_id>/users/<user_org_id>/events?<data..>")]
|
||||||
async fn get_user_events(
|
async fn get_user_events(
|
||||||
org_id: String,
|
org_id: &str,
|
||||||
user_org_id: String,
|
user_org_id: &str,
|
||||||
data: EventRange,
|
data: EventRange,
|
||||||
_headers: AdminHeaders,
|
_headers: AdminHeaders,
|
||||||
mut conn: DbConn,
|
mut conn: DbConn,
|
||||||
@@ -111,7 +111,7 @@ async fn get_user_events(
|
|||||||
parse_date(&data.end)
|
parse_date(&data.end)
|
||||||
};
|
};
|
||||||
|
|
||||||
Event::find_by_org_and_user_org(&org_id, &user_org_id, &start_date, &end_date, &mut conn)
|
Event::find_by_org_and_user_org(org_id, user_org_id, &start_date, &end_date, &mut conn)
|
||||||
.await
|
.await
|
||||||
.iter()
|
.iter()
|
||||||
.map(|e| e.to_json())
|
.map(|e| e.to_json())
|
||||||
@@ -185,7 +185,7 @@ async fn post_events_collect(data: JsonUpcaseVec<EventCollection>, headers: Head
|
|||||||
_log_event(
|
_log_event(
|
||||||
event.Type,
|
event.Type,
|
||||||
org_uuid,
|
org_uuid,
|
||||||
String::from(org_uuid),
|
org_uuid,
|
||||||
&headers.user.uuid,
|
&headers.user.uuid,
|
||||||
headers.device.atype,
|
headers.device.atype,
|
||||||
Some(event_date),
|
Some(event_date),
|
||||||
@@ -202,7 +202,7 @@ async fn post_events_collect(data: JsonUpcaseVec<EventCollection>, headers: Head
|
|||||||
_log_event(
|
_log_event(
|
||||||
event.Type,
|
event.Type,
|
||||||
cipher_uuid,
|
cipher_uuid,
|
||||||
org_uuid,
|
&org_uuid,
|
||||||
&headers.user.uuid,
|
&headers.user.uuid,
|
||||||
headers.device.atype,
|
headers.device.atype,
|
||||||
Some(event_date),
|
Some(event_date),
|
||||||
@@ -262,7 +262,7 @@ async fn _log_user_event(
|
|||||||
pub async fn log_event(
|
pub async fn log_event(
|
||||||
event_type: i32,
|
event_type: i32,
|
||||||
source_uuid: &str,
|
source_uuid: &str,
|
||||||
org_uuid: String,
|
org_uuid: &str,
|
||||||
act_user_uuid: String,
|
act_user_uuid: String,
|
||||||
device_type: i32,
|
device_type: i32,
|
||||||
ip: &IpAddr,
|
ip: &IpAddr,
|
||||||
@@ -278,7 +278,7 @@ pub async fn log_event(
|
|||||||
async fn _log_event(
|
async fn _log_event(
|
||||||
event_type: i32,
|
event_type: i32,
|
||||||
source_uuid: &str,
|
source_uuid: &str,
|
||||||
org_uuid: String,
|
org_uuid: &str,
|
||||||
act_user_uuid: &str,
|
act_user_uuid: &str,
|
||||||
device_type: i32,
|
device_type: i32,
|
||||||
event_date: Option<NaiveDateTime>,
|
event_date: Option<NaiveDateTime>,
|
||||||
@@ -314,7 +314,7 @@ async fn _log_event(
|
|||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
event.org_uuid = Some(org_uuid);
|
event.org_uuid = Some(String::from(org_uuid));
|
||||||
event.act_user_uuid = Some(String::from(act_user_uuid));
|
event.act_user_uuid = Some(String::from(act_user_uuid));
|
||||||
event.device_type = Some(device_type);
|
event.device_type = Some(device_type);
|
||||||
event.ip_address = Some(ip.to_string());
|
event.ip_address = Some(ip.to_string());
|
||||||
|
@@ -24,8 +24,8 @@ async fn get_folders(headers: Headers, mut conn: DbConn) -> Json<Value> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[get("/folders/<uuid>")]
|
#[get("/folders/<uuid>")]
|
||||||
async fn get_folder(uuid: String, headers: Headers, mut conn: DbConn) -> JsonResult {
|
async fn get_folder(uuid: &str, headers: Headers, mut conn: DbConn) -> JsonResult {
|
||||||
let folder = match Folder::find_by_uuid(&uuid, &mut conn).await {
|
let folder = match Folder::find_by_uuid(uuid, &mut conn).await {
|
||||||
Some(folder) => folder,
|
Some(folder) => folder,
|
||||||
_ => err!("Invalid folder"),
|
_ => err!("Invalid folder"),
|
||||||
};
|
};
|
||||||
@@ -50,14 +50,14 @@ async fn post_folders(data: JsonUpcase<FolderData>, headers: Headers, mut conn:
|
|||||||
let mut folder = Folder::new(headers.user.uuid, data.Name);
|
let mut folder = Folder::new(headers.user.uuid, data.Name);
|
||||||
|
|
||||||
folder.save(&mut conn).await?;
|
folder.save(&mut conn).await?;
|
||||||
nt.send_folder_update(UpdateType::SyncFolderCreate, &folder, &headers.device.uuid).await;
|
nt.send_folder_update(UpdateType::SyncFolderCreate, &folder, &headers.device.uuid, &mut conn).await;
|
||||||
|
|
||||||
Ok(Json(folder.to_json()))
|
Ok(Json(folder.to_json()))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/folders/<uuid>", data = "<data>")]
|
#[post("/folders/<uuid>", data = "<data>")]
|
||||||
async fn post_folder(
|
async fn post_folder(
|
||||||
uuid: String,
|
uuid: &str,
|
||||||
data: JsonUpcase<FolderData>,
|
data: JsonUpcase<FolderData>,
|
||||||
headers: Headers,
|
headers: Headers,
|
||||||
conn: DbConn,
|
conn: DbConn,
|
||||||
@@ -68,7 +68,7 @@ async fn post_folder(
|
|||||||
|
|
||||||
#[put("/folders/<uuid>", data = "<data>")]
|
#[put("/folders/<uuid>", data = "<data>")]
|
||||||
async fn put_folder(
|
async fn put_folder(
|
||||||
uuid: String,
|
uuid: &str,
|
||||||
data: JsonUpcase<FolderData>,
|
data: JsonUpcase<FolderData>,
|
||||||
headers: Headers,
|
headers: Headers,
|
||||||
mut conn: DbConn,
|
mut conn: DbConn,
|
||||||
@@ -76,7 +76,7 @@ async fn put_folder(
|
|||||||
) -> JsonResult {
|
) -> JsonResult {
|
||||||
let data: FolderData = data.into_inner().data;
|
let data: FolderData = data.into_inner().data;
|
||||||
|
|
||||||
let mut folder = match Folder::find_by_uuid(&uuid, &mut conn).await {
|
let mut folder = match Folder::find_by_uuid(uuid, &mut conn).await {
|
||||||
Some(folder) => folder,
|
Some(folder) => folder,
|
||||||
_ => err!("Invalid folder"),
|
_ => err!("Invalid folder"),
|
||||||
};
|
};
|
||||||
@@ -88,19 +88,19 @@ async fn put_folder(
|
|||||||
folder.name = data.Name;
|
folder.name = data.Name;
|
||||||
|
|
||||||
folder.save(&mut conn).await?;
|
folder.save(&mut conn).await?;
|
||||||
nt.send_folder_update(UpdateType::SyncFolderUpdate, &folder, &headers.device.uuid).await;
|
nt.send_folder_update(UpdateType::SyncFolderUpdate, &folder, &headers.device.uuid, &mut conn).await;
|
||||||
|
|
||||||
Ok(Json(folder.to_json()))
|
Ok(Json(folder.to_json()))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/folders/<uuid>/delete")]
|
#[post("/folders/<uuid>/delete")]
|
||||||
async fn delete_folder_post(uuid: String, headers: Headers, conn: DbConn, nt: Notify<'_>) -> EmptyResult {
|
async fn delete_folder_post(uuid: &str, headers: Headers, conn: DbConn, nt: Notify<'_>) -> EmptyResult {
|
||||||
delete_folder(uuid, headers, conn, nt).await
|
delete_folder(uuid, headers, conn, nt).await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[delete("/folders/<uuid>")]
|
#[delete("/folders/<uuid>")]
|
||||||
async fn delete_folder(uuid: String, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult {
|
async fn delete_folder(uuid: &str, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult {
|
||||||
let folder = match Folder::find_by_uuid(&uuid, &mut conn).await {
|
let folder = match Folder::find_by_uuid(uuid, &mut conn).await {
|
||||||
Some(folder) => folder,
|
Some(folder) => folder,
|
||||||
_ => err!("Invalid folder"),
|
_ => err!("Invalid folder"),
|
||||||
};
|
};
|
||||||
@@ -112,6 +112,6 @@ async fn delete_folder(uuid: String, headers: Headers, mut conn: DbConn, nt: Not
|
|||||||
// Delete the actual folder entry
|
// Delete the actual folder entry
|
||||||
folder.delete(&mut conn).await?;
|
folder.delete(&mut conn).await?;
|
||||||
|
|
||||||
nt.send_folder_update(UpdateType::SyncFolderDelete, &folder, &headers.device.uuid).await;
|
nt.send_folder_update(UpdateType::SyncFolderDelete, &folder, &headers.device.uuid, &mut conn).await;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@@ -4,6 +4,7 @@ mod emergency_access;
|
|||||||
mod events;
|
mod events;
|
||||||
mod folders;
|
mod folders;
|
||||||
mod organizations;
|
mod organizations;
|
||||||
|
mod public;
|
||||||
mod sends;
|
mod sends;
|
||||||
pub mod two_factor;
|
pub mod two_factor;
|
||||||
|
|
||||||
@@ -14,7 +15,6 @@ pub use sends::purge_sends;
|
|||||||
pub use two_factor::send_incomplete_2fa_notifications;
|
pub use two_factor::send_incomplete_2fa_notifications;
|
||||||
|
|
||||||
pub fn routes() -> Vec<Route> {
|
pub fn routes() -> Vec<Route> {
|
||||||
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, config];
|
let mut meta_routes = routes![alive, now, version, config];
|
||||||
@@ -28,7 +28,7 @@ pub fn routes() -> Vec<Route> {
|
|||||||
routes.append(&mut organizations::routes());
|
routes.append(&mut organizations::routes());
|
||||||
routes.append(&mut two_factor::routes());
|
routes.append(&mut two_factor::routes());
|
||||||
routes.append(&mut sends::routes());
|
routes.append(&mut sends::routes());
|
||||||
routes.append(&mut device_token_routes);
|
routes.append(&mut public::routes());
|
||||||
routes.append(&mut eq_domains_routes);
|
routes.append(&mut eq_domains_routes);
|
||||||
routes.append(&mut hibp_routes);
|
routes.append(&mut hibp_routes);
|
||||||
routes.append(&mut meta_routes);
|
routes.append(&mut meta_routes);
|
||||||
@@ -57,37 +57,6 @@ use crate::{
|
|||||||
util::get_reqwest_client,
|
util::get_reqwest_client,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[put("/devices/identifier/<uuid>/clear-token")]
|
|
||||||
fn clear_device_token(uuid: String) -> &'static str {
|
|
||||||
// This endpoint doesn't have auth header
|
|
||||||
|
|
||||||
let _ = uuid;
|
|
||||||
// uuid is not related to deviceId
|
|
||||||
|
|
||||||
// This only clears push token
|
|
||||||
// https://github.com/bitwarden/core/blob/master/src/Api/Controllers/DevicesController.cs#L109
|
|
||||||
// https://github.com/bitwarden/core/blob/master/src/Core/Services/Implementations/DeviceService.cs#L37
|
|
||||||
""
|
|
||||||
}
|
|
||||||
|
|
||||||
#[put("/devices/identifier/<uuid>/token", data = "<data>")]
|
|
||||||
fn put_device_token(uuid: String, data: JsonUpcase<Value>, headers: Headers) -> Json<Value> {
|
|
||||||
let _data: Value = data.into_inner().data;
|
|
||||||
// Data has a single string value "PushToken"
|
|
||||||
let _ = uuid;
|
|
||||||
// uuid is not related to deviceId
|
|
||||||
|
|
||||||
// TODO: This should save the push token, but we don't have push functionality
|
|
||||||
|
|
||||||
Json(json!({
|
|
||||||
"Id": headers.device.uuid,
|
|
||||||
"Name": headers.device.name,
|
|
||||||
"Type": headers.device.atype,
|
|
||||||
"Identifier": headers.device.uuid,
|
|
||||||
"CreationDate": crate::util::format_date(&headers.device.created_at),
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
struct GlobalDomain {
|
struct GlobalDomain {
|
||||||
@@ -170,7 +139,7 @@ async fn put_eq_domains(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[get("/hibp/breach?<username>")]
|
#[get("/hibp/breach?<username>")]
|
||||||
async fn hibp_breach(username: String) -> JsonResult {
|
async fn hibp_breach(username: &str) -> JsonResult {
|
||||||
let url = format!(
|
let url = format!(
|
||||||
"https://haveibeenpwned.com/api/v3/breachedaccount/{username}?truncateResponse=false&includeUnverified=false"
|
"https://haveibeenpwned.com/api/v3/breachedaccount/{username}?truncateResponse=false&includeUnverified=false"
|
||||||
);
|
);
|
||||||
|
File diff suppressed because it is too large
Load Diff
238
src/api/core/public.rs
Normal file
238
src/api/core/public.rs
Normal file
@@ -0,0 +1,238 @@
|
|||||||
|
use chrono::Utc;
|
||||||
|
use rocket::{
|
||||||
|
request::{self, FromRequest, Outcome},
|
||||||
|
Request, Route,
|
||||||
|
};
|
||||||
|
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
api::{EmptyResult, JsonUpcase},
|
||||||
|
auth,
|
||||||
|
db::{models::*, DbConn},
|
||||||
|
mail, CONFIG,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn routes() -> Vec<Route> {
|
||||||
|
routes![ldap_import]
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
struct OrgImportGroupData {
|
||||||
|
Name: String,
|
||||||
|
ExternalId: String,
|
||||||
|
MemberExternalIds: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
struct OrgImportUserData {
|
||||||
|
Email: String,
|
||||||
|
ExternalId: String,
|
||||||
|
Deleted: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
struct OrgImportData {
|
||||||
|
Groups: Vec<OrgImportGroupData>,
|
||||||
|
Members: Vec<OrgImportUserData>,
|
||||||
|
OverwriteExisting: bool,
|
||||||
|
// LargeImport: bool, // For now this will not be used, upstream uses this to prevent syncs of more then 2000 users or groups without the flag set.
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/public/organization/import", data = "<data>")]
|
||||||
|
async fn ldap_import(data: JsonUpcase<OrgImportData>, token: PublicToken, mut conn: DbConn) -> EmptyResult {
|
||||||
|
// Most of the logic for this function can be found here
|
||||||
|
// https://github.com/bitwarden/server/blob/fd892b2ff4547648a276734fb2b14a8abae2c6f5/src/Core/Services/Implementations/OrganizationService.cs#L1797
|
||||||
|
|
||||||
|
let org_id = token.0;
|
||||||
|
let data = data.into_inner().data;
|
||||||
|
|
||||||
|
for user_data in &data.Members {
|
||||||
|
if user_data.Deleted {
|
||||||
|
// If user is marked for deletion and it exists, revoke it
|
||||||
|
if let Some(mut user_org) =
|
||||||
|
UserOrganization::find_by_email_and_org(&user_data.Email, &org_id, &mut conn).await
|
||||||
|
{
|
||||||
|
user_org.revoke();
|
||||||
|
user_org.save(&mut conn).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If user is part of the organization, restore it
|
||||||
|
} else if let Some(mut user_org) =
|
||||||
|
UserOrganization::find_by_email_and_org(&user_data.Email, &org_id, &mut conn).await
|
||||||
|
{
|
||||||
|
if user_org.status < UserOrgStatus::Revoked as i32 {
|
||||||
|
user_org.restore();
|
||||||
|
user_org.save(&mut conn).await?;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If user is not part of the organization
|
||||||
|
let user = match User::find_by_mail(&user_data.Email, &mut conn).await {
|
||||||
|
Some(user) => user, // exists in vaultwarden
|
||||||
|
None => {
|
||||||
|
// doesn't exist in vaultwarden
|
||||||
|
let mut new_user = User::new(user_data.Email.clone());
|
||||||
|
new_user.set_external_id(Some(user_data.ExternalId.clone()));
|
||||||
|
new_user.save(&mut conn).await?;
|
||||||
|
|
||||||
|
if !CONFIG.mail_enabled() {
|
||||||
|
let invitation = Invitation::new(&new_user.email);
|
||||||
|
invitation.save(&mut conn).await?;
|
||||||
|
}
|
||||||
|
new_user
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let user_org_status = if CONFIG.mail_enabled() {
|
||||||
|
UserOrgStatus::Invited as i32
|
||||||
|
} else {
|
||||||
|
UserOrgStatus::Accepted as i32 // Automatically mark user as accepted if no email invites
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut new_org_user = UserOrganization::new(user.uuid.clone(), org_id.clone());
|
||||||
|
new_org_user.access_all = false;
|
||||||
|
new_org_user.atype = UserOrgType::User as i32;
|
||||||
|
new_org_user.status = user_org_status;
|
||||||
|
|
||||||
|
new_org_user.save(&mut conn).await?;
|
||||||
|
|
||||||
|
if CONFIG.mail_enabled() {
|
||||||
|
let (org_name, org_email) = match Organization::find_by_uuid(&org_id, &mut conn).await {
|
||||||
|
Some(org) => (org.name, org.billing_email),
|
||||||
|
None => err!("Error looking up organization"),
|
||||||
|
};
|
||||||
|
|
||||||
|
mail::send_invite(
|
||||||
|
&user_data.Email,
|
||||||
|
&user.uuid,
|
||||||
|
Some(org_id.clone()),
|
||||||
|
Some(new_org_user.uuid),
|
||||||
|
&org_name,
|
||||||
|
Some(org_email),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if CONFIG.org_groups_enabled() {
|
||||||
|
for group_data in &data.Groups {
|
||||||
|
let group_uuid = match Group::find_by_external_id(&group_data.ExternalId, &mut conn).await {
|
||||||
|
Some(group) => group.uuid,
|
||||||
|
None => {
|
||||||
|
let mut group =
|
||||||
|
Group::new(org_id.clone(), group_data.Name.clone(), false, Some(group_data.ExternalId.clone()));
|
||||||
|
group.save(&mut conn).await?;
|
||||||
|
group.uuid
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
GroupUser::delete_all_by_group(&group_uuid, &mut conn).await?;
|
||||||
|
|
||||||
|
for ext_id in &group_data.MemberExternalIds {
|
||||||
|
if let Some(user) = User::find_by_external_id(ext_id, &mut conn).await {
|
||||||
|
if let Some(user_org) = UserOrganization::find_by_user_and_org(&user.uuid, &org_id, &mut conn).await
|
||||||
|
{
|
||||||
|
let mut group_user = GroupUser::new(group_uuid.clone(), user_org.uuid.clone());
|
||||||
|
group_user.save(&mut conn).await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
warn!("Group support is disabled, groups will not be imported!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
// Generate a HashSet to quickly verify if a member is listed or not.
|
||||||
|
let sync_members: HashSet<String> = data.Members.into_iter().map(|m| m.ExternalId).collect();
|
||||||
|
for user_org in UserOrganization::find_by_org(&org_id, &mut conn).await {
|
||||||
|
if let Some(user_external_id) =
|
||||||
|
User::find_by_uuid(&user_org.user_uuid, &mut conn).await.map(|u| u.external_id)
|
||||||
|
{
|
||||||
|
if user_external_id.is_some() && !sync_members.contains(&user_external_id.unwrap()) {
|
||||||
|
if user_org.atype == UserOrgType::Owner && user_org.status == UserOrgStatus::Confirmed as i32 {
|
||||||
|
// Removing owner, check that there is at least one other confirmed owner
|
||||||
|
if UserOrganization::count_confirmed_by_org_and_type(&org_id, UserOrgType::Owner, &mut conn)
|
||||||
|
.await
|
||||||
|
<= 1
|
||||||
|
{
|
||||||
|
warn!("Can't delete the last owner");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
user_org.delete(&mut conn).await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct PublicToken(String);
|
||||||
|
|
||||||
|
#[rocket::async_trait]
|
||||||
|
impl<'r> FromRequest<'r> for PublicToken {
|
||||||
|
type Error = &'static str;
|
||||||
|
|
||||||
|
async fn from_request(request: &'r Request<'_>) -> request::Outcome<Self, Self::Error> {
|
||||||
|
let headers = request.headers();
|
||||||
|
// Get access_token
|
||||||
|
let access_token: &str = match headers.get_one("Authorization") {
|
||||||
|
Some(a) => match a.rsplit("Bearer ").next() {
|
||||||
|
Some(split) => split,
|
||||||
|
None => err_handler!("No access token provided"),
|
||||||
|
},
|
||||||
|
None => err_handler!("No access token provided"),
|
||||||
|
};
|
||||||
|
// Check JWT token is valid and get device and user from it
|
||||||
|
let claims = match auth::decode_api_org(access_token) {
|
||||||
|
Ok(claims) => claims,
|
||||||
|
Err(_) => err_handler!("Invalid claim"),
|
||||||
|
};
|
||||||
|
// Check if time is between claims.nbf and claims.exp
|
||||||
|
let time_now = Utc::now().naive_utc().timestamp();
|
||||||
|
if time_now < claims.nbf {
|
||||||
|
err_handler!("Token issued in the future");
|
||||||
|
}
|
||||||
|
if time_now > claims.exp {
|
||||||
|
err_handler!("Token expired");
|
||||||
|
}
|
||||||
|
// Check if claims.iss is host|claims.scope[0]
|
||||||
|
let host = match auth::Host::from_request(request).await {
|
||||||
|
Outcome::Success(host) => host,
|
||||||
|
_ => err_handler!("Error getting Host"),
|
||||||
|
};
|
||||||
|
let complete_host = format!("{}|{}", host.host, claims.scope[0]);
|
||||||
|
if complete_host != claims.iss {
|
||||||
|
err_handler!("Token not issued by this server");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if claims.sub is org_api_key.uuid
|
||||||
|
// Check if claims.client_sub is org_api_key.org_uuid
|
||||||
|
let conn = match DbConn::from_request(request).await {
|
||||||
|
Outcome::Success(conn) => conn,
|
||||||
|
_ => err_handler!("Error getting DB"),
|
||||||
|
};
|
||||||
|
let org_uuid = match claims.client_id.strip_prefix("organization.") {
|
||||||
|
Some(uuid) => uuid,
|
||||||
|
None => err_handler!("Malformed client_id"),
|
||||||
|
};
|
||||||
|
let org_api_key = match OrganizationApiKey::find_by_org_uuid(org_uuid, &conn).await {
|
||||||
|
Some(org_api_key) => org_api_key,
|
||||||
|
None => err_handler!("Invalid client_id"),
|
||||||
|
};
|
||||||
|
if org_api_key.org_uuid != claims.client_sub {
|
||||||
|
err_handler!("Token not issued for this org");
|
||||||
|
}
|
||||||
|
if org_api_key.uuid != claims.sub {
|
||||||
|
err_handler!("Token not issued for this client");
|
||||||
|
}
|
||||||
|
|
||||||
|
Outcome::Success(PublicToken(claims.client_sub))
|
||||||
|
}
|
||||||
|
}
|
@@ -154,8 +154,8 @@ async fn get_sends(headers: Headers, mut conn: DbConn) -> Json<Value> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[get("/sends/<uuid>")]
|
#[get("/sends/<uuid>")]
|
||||||
async fn get_send(uuid: String, headers: Headers, mut conn: DbConn) -> JsonResult {
|
async fn get_send(uuid: &str, headers: Headers, mut conn: DbConn) -> JsonResult {
|
||||||
let send = match Send::find_by_uuid(&uuid, &mut conn).await {
|
let send = match Send::find_by_uuid(uuid, &mut conn).await {
|
||||||
Some(send) => send,
|
Some(send) => send,
|
||||||
None => err!("Send not found"),
|
None => err!("Send not found"),
|
||||||
};
|
};
|
||||||
@@ -180,7 +180,14 @@ async fn post_send(data: JsonUpcase<SendData>, headers: Headers, mut conn: DbCon
|
|||||||
|
|
||||||
let mut send = create_send(data, headers.user.uuid)?;
|
let mut send = create_send(data, headers.user.uuid)?;
|
||||||
send.save(&mut conn).await?;
|
send.save(&mut conn).await?;
|
||||||
nt.send_send_update(UpdateType::SyncSendCreate, &send, &send.update_users_revision(&mut conn).await).await;
|
nt.send_send_update(
|
||||||
|
UpdateType::SyncSendCreate,
|
||||||
|
&send,
|
||||||
|
&send.update_users_revision(&mut conn).await,
|
||||||
|
&headers.device.uuid,
|
||||||
|
&mut conn,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
Ok(Json(send.to_json()))
|
Ok(Json(send.to_json()))
|
||||||
}
|
}
|
||||||
@@ -252,7 +259,14 @@ async fn post_send_file(data: Form<UploadData<'_>>, headers: Headers, mut conn:
|
|||||||
|
|
||||||
// Save the changes in the database
|
// Save the changes in the database
|
||||||
send.save(&mut conn).await?;
|
send.save(&mut conn).await?;
|
||||||
nt.send_send_update(UpdateType::SyncSendCreate, &send, &send.update_users_revision(&mut conn).await).await;
|
nt.send_send_update(
|
||||||
|
UpdateType::SyncSendCreate,
|
||||||
|
&send,
|
||||||
|
&send.update_users_revision(&mut conn).await,
|
||||||
|
&headers.device.uuid,
|
||||||
|
&mut conn,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
Ok(Json(send.to_json()))
|
Ok(Json(send.to_json()))
|
||||||
}
|
}
|
||||||
@@ -315,8 +329,8 @@ async fn post_send_file_v2(data: JsonUpcase<SendData>, headers: Headers, mut con
|
|||||||
// https://github.com/bitwarden/server/blob/d0c793c95181dfb1b447eb450f85ba0bfd7ef643/src/Api/Controllers/SendsController.cs#L243
|
// 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>")]
|
#[post("/sends/<send_uuid>/file/<file_id>", format = "multipart/form-data", data = "<data>")]
|
||||||
async fn post_send_file_v2_data(
|
async fn post_send_file_v2_data(
|
||||||
send_uuid: String,
|
send_uuid: &str,
|
||||||
file_id: String,
|
file_id: &str,
|
||||||
data: Form<UploadDataV2<'_>>,
|
data: Form<UploadDataV2<'_>>,
|
||||||
headers: Headers,
|
headers: Headers,
|
||||||
mut conn: DbConn,
|
mut conn: DbConn,
|
||||||
@@ -326,20 +340,30 @@ async fn post_send_file_v2_data(
|
|||||||
|
|
||||||
let mut data = data.into_inner();
|
let mut data = data.into_inner();
|
||||||
|
|
||||||
if let Some(send) = Send::find_by_uuid(&send_uuid, &mut conn).await {
|
let Some(send) = Send::find_by_uuid(send_uuid, &mut conn).await else { err!("Send not found. Unable to save the file.") };
|
||||||
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 {
|
let Some(send_user_id) = &send.user_uuid else {err!("Sends are only supported for users at the moment")};
|
||||||
data.data.move_copy_to(file_path).await?
|
if send_user_id != &headers.user.uuid {
|
||||||
}
|
err!("Send doesn't belong to user");
|
||||||
|
|
||||||
nt.send_send_update(UpdateType::SyncSendCreate, &send, &send.update_users_revision(&mut conn).await).await;
|
|
||||||
} else {
|
|
||||||
err!("Send not found. Unable to save the file.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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(&mut conn).await,
|
||||||
|
&headers.device.uuid,
|
||||||
|
&mut conn,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -351,13 +375,13 @@ pub struct SendAccessData {
|
|||||||
|
|
||||||
#[post("/sends/access/<access_id>", data = "<data>")]
|
#[post("/sends/access/<access_id>", data = "<data>")]
|
||||||
async fn post_access(
|
async fn post_access(
|
||||||
access_id: String,
|
access_id: &str,
|
||||||
data: JsonUpcase<SendAccessData>,
|
data: JsonUpcase<SendAccessData>,
|
||||||
mut conn: DbConn,
|
mut conn: DbConn,
|
||||||
ip: ClientIp,
|
ip: ClientIp,
|
||||||
nt: Notify<'_>,
|
nt: Notify<'_>,
|
||||||
) -> JsonResult {
|
) -> JsonResult {
|
||||||
let mut send = match Send::find_by_access_id(&access_id, &mut conn).await {
|
let mut send = match Send::find_by_access_id(access_id, &mut conn).await {
|
||||||
Some(s) => s,
|
Some(s) => s,
|
||||||
None => err_code!(SEND_INACCESSIBLE_MSG, 404),
|
None => err_code!(SEND_INACCESSIBLE_MSG, 404),
|
||||||
};
|
};
|
||||||
@@ -397,21 +421,28 @@ async fn post_access(
|
|||||||
|
|
||||||
send.save(&mut conn).await?;
|
send.save(&mut conn).await?;
|
||||||
|
|
||||||
nt.send_send_update(UpdateType::SyncSendUpdate, &send, &send.update_users_revision(&mut conn).await).await;
|
nt.send_send_update(
|
||||||
|
UpdateType::SyncSendUpdate,
|
||||||
|
&send,
|
||||||
|
&send.update_users_revision(&mut conn).await,
|
||||||
|
&String::from("00000000-0000-0000-0000-000000000000"),
|
||||||
|
&mut conn,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
Ok(Json(send.to_json_access(&mut conn).await))
|
Ok(Json(send.to_json_access(&mut conn).await))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/sends/<send_id>/access/file/<file_id>", data = "<data>")]
|
#[post("/sends/<send_id>/access/file/<file_id>", data = "<data>")]
|
||||||
async fn post_access_file(
|
async fn post_access_file(
|
||||||
send_id: String,
|
send_id: &str,
|
||||||
file_id: String,
|
file_id: &str,
|
||||||
data: JsonUpcase<SendAccessData>,
|
data: JsonUpcase<SendAccessData>,
|
||||||
host: Host,
|
host: Host,
|
||||||
mut conn: DbConn,
|
mut conn: DbConn,
|
||||||
nt: Notify<'_>,
|
nt: Notify<'_>,
|
||||||
) -> JsonResult {
|
) -> JsonResult {
|
||||||
let mut send = match Send::find_by_uuid(&send_id, &mut conn).await {
|
let mut send = match Send::find_by_uuid(send_id, &mut conn).await {
|
||||||
Some(s) => s,
|
Some(s) => s,
|
||||||
None => err_code!(SEND_INACCESSIBLE_MSG, 404),
|
None => err_code!(SEND_INACCESSIBLE_MSG, 404),
|
||||||
};
|
};
|
||||||
@@ -448,9 +479,16 @@ async fn post_access_file(
|
|||||||
|
|
||||||
send.save(&mut conn).await?;
|
send.save(&mut conn).await?;
|
||||||
|
|
||||||
nt.send_send_update(UpdateType::SyncSendUpdate, &send, &send.update_users_revision(&mut conn).await).await;
|
nt.send_send_update(
|
||||||
|
UpdateType::SyncSendUpdate,
|
||||||
|
&send,
|
||||||
|
&send.update_users_revision(&mut conn).await,
|
||||||
|
&String::from("00000000-0000-0000-0000-000000000000"),
|
||||||
|
&mut conn,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
let token_claims = crate::auth::generate_send_claims(&send_id, &file_id);
|
let token_claims = crate::auth::generate_send_claims(send_id, file_id);
|
||||||
let token = crate::auth::encode_jwt(&token_claims);
|
let token = crate::auth::encode_jwt(&token_claims);
|
||||||
Ok(Json(json!({
|
Ok(Json(json!({
|
||||||
"Object": "send-fileDownload",
|
"Object": "send-fileDownload",
|
||||||
@@ -460,8 +498,8 @@ async fn post_access_file(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[get("/sends/<send_id>/<file_id>?<t>")]
|
#[get("/sends/<send_id>/<file_id>?<t>")]
|
||||||
async fn download_send(send_id: SafeString, file_id: SafeString, t: String) -> Option<NamedFile> {
|
async fn download_send(send_id: SafeString, file_id: SafeString, t: &str) -> Option<NamedFile> {
|
||||||
if let Ok(claims) = crate::auth::decode_send(&t) {
|
if let Ok(claims) = crate::auth::decode_send(t) {
|
||||||
if claims.sub == format!("{send_id}/{file_id}") {
|
if claims.sub == format!("{send_id}/{file_id}") {
|
||||||
return NamedFile::open(Path::new(&CONFIG.sends_folder()).join(send_id).join(file_id)).await.ok();
|
return NamedFile::open(Path::new(&CONFIG.sends_folder()).join(send_id).join(file_id)).await.ok();
|
||||||
}
|
}
|
||||||
@@ -471,7 +509,7 @@ async fn download_send(send_id: SafeString, file_id: SafeString, t: String) -> O
|
|||||||
|
|
||||||
#[put("/sends/<id>", data = "<data>")]
|
#[put("/sends/<id>", data = "<data>")]
|
||||||
async fn put_send(
|
async fn put_send(
|
||||||
id: String,
|
id: &str,
|
||||||
data: JsonUpcase<SendData>,
|
data: JsonUpcase<SendData>,
|
||||||
headers: Headers,
|
headers: Headers,
|
||||||
mut conn: DbConn,
|
mut conn: DbConn,
|
||||||
@@ -482,7 +520,7 @@ async fn put_send(
|
|||||||
let data: SendData = data.into_inner().data;
|
let data: SendData = data.into_inner().data;
|
||||||
enforce_disable_hide_email_policy(&data, &headers, &mut conn).await?;
|
enforce_disable_hide_email_policy(&data, &headers, &mut conn).await?;
|
||||||
|
|
||||||
let mut send = match Send::find_by_uuid(&id, &mut conn).await {
|
let mut send = match Send::find_by_uuid(id, &mut conn).await {
|
||||||
Some(s) => s,
|
Some(s) => s,
|
||||||
None => err!("Send not found"),
|
None => err!("Send not found"),
|
||||||
};
|
};
|
||||||
@@ -530,14 +568,21 @@ async fn put_send(
|
|||||||
}
|
}
|
||||||
|
|
||||||
send.save(&mut conn).await?;
|
send.save(&mut conn).await?;
|
||||||
nt.send_send_update(UpdateType::SyncSendUpdate, &send, &send.update_users_revision(&mut conn).await).await;
|
nt.send_send_update(
|
||||||
|
UpdateType::SyncSendUpdate,
|
||||||
|
&send,
|
||||||
|
&send.update_users_revision(&mut conn).await,
|
||||||
|
&headers.device.uuid,
|
||||||
|
&mut conn,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
Ok(Json(send.to_json()))
|
Ok(Json(send.to_json()))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[delete("/sends/<id>")]
|
#[delete("/sends/<id>")]
|
||||||
async fn delete_send(id: String, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult {
|
async fn delete_send(id: &str, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult {
|
||||||
let send = match Send::find_by_uuid(&id, &mut conn).await {
|
let send = match Send::find_by_uuid(id, &mut conn).await {
|
||||||
Some(s) => s,
|
Some(s) => s,
|
||||||
None => err!("Send not found"),
|
None => err!("Send not found"),
|
||||||
};
|
};
|
||||||
@@ -547,16 +592,23 @@ async fn delete_send(id: String, headers: Headers, mut conn: DbConn, nt: Notify<
|
|||||||
}
|
}
|
||||||
|
|
||||||
send.delete(&mut conn).await?;
|
send.delete(&mut conn).await?;
|
||||||
nt.send_send_update(UpdateType::SyncSendDelete, &send, &send.update_users_revision(&mut conn).await).await;
|
nt.send_send_update(
|
||||||
|
UpdateType::SyncSendDelete,
|
||||||
|
&send,
|
||||||
|
&send.update_users_revision(&mut conn).await,
|
||||||
|
&headers.device.uuid,
|
||||||
|
&mut conn,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[put("/sends/<id>/remove-password")]
|
#[put("/sends/<id>/remove-password")]
|
||||||
async fn put_remove_password(id: String, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> JsonResult {
|
async fn put_remove_password(id: &str, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> JsonResult {
|
||||||
enforce_disable_send_policy(&headers, &mut conn).await?;
|
enforce_disable_send_policy(&headers, &mut conn).await?;
|
||||||
|
|
||||||
let mut send = match Send::find_by_uuid(&id, &mut conn).await {
|
let mut send = match Send::find_by_uuid(id, &mut conn).await {
|
||||||
Some(s) => s,
|
Some(s) => s,
|
||||||
None => err!("Send not found"),
|
None => err!("Send not found"),
|
||||||
};
|
};
|
||||||
@@ -567,7 +619,14 @@ async fn put_remove_password(id: String, headers: Headers, mut conn: DbConn, nt:
|
|||||||
|
|
||||||
send.set_password(None);
|
send.set_password(None);
|
||||||
send.save(&mut conn).await?;
|
send.save(&mut conn).await?;
|
||||||
nt.send_send_update(UpdateType::SyncSendUpdate, &send, &send.update_users_revision(&mut conn).await).await;
|
nt.send_send_update(
|
||||||
|
UpdateType::SyncSendUpdate,
|
||||||
|
&send,
|
||||||
|
&send.update_users_revision(&mut conn).await,
|
||||||
|
&headers.device.uuid,
|
||||||
|
&mut conn,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
Ok(Json(send.to_json()))
|
Ok(Json(send.to_json()))
|
||||||
}
|
}
|
||||||
|
@@ -97,15 +97,15 @@ async fn icon_redirect(domain: &str, template: &str) -> Option<Redirect> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[get("/<domain>/icon.png")]
|
#[get("/<domain>/icon.png")]
|
||||||
async fn icon_external(domain: String) -> Option<Redirect> {
|
async fn icon_external(domain: &str) -> Option<Redirect> {
|
||||||
icon_redirect(&domain, &CONFIG._icon_service_url()).await
|
icon_redirect(domain, &CONFIG._icon_service_url()).await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/<domain>/icon.png")]
|
#[get("/<domain>/icon.png")]
|
||||||
async fn icon_internal(domain: String) -> Cached<(ContentType, Vec<u8>)> {
|
async fn icon_internal(domain: &str) -> Cached<(ContentType, Vec<u8>)> {
|
||||||
const FALLBACK_ICON: &[u8] = include_bytes!("../static/images/fallback-icon.png");
|
const FALLBACK_ICON: &[u8] = include_bytes!("../static/images/fallback-icon.png");
|
||||||
|
|
||||||
if !is_valid_domain(&domain) {
|
if !is_valid_domain(domain) {
|
||||||
warn!("Invalid domain: {}", domain);
|
warn!("Invalid domain: {}", domain);
|
||||||
return Cached::ttl(
|
return Cached::ttl(
|
||||||
(ContentType::new("image", "png"), FALLBACK_ICON.to_vec()),
|
(ContentType::new("image", "png"), FALLBACK_ICON.to_vec()),
|
||||||
@@ -114,7 +114,7 @@ async fn icon_internal(domain: String) -> Cached<(ContentType, Vec<u8>)> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
match get_icon(&domain).await {
|
match get_icon(domain).await {
|
||||||
Some((icon, icon_type)) => {
|
Some((icon, icon_type)) => {
|
||||||
Cached::ttl((ContentType::new("image", icon_type), icon), CONFIG.icon_cache_ttl(), true)
|
Cached::ttl((ContentType::new("image", icon_type), icon), CONFIG.icon_cache_ttl(), true)
|
||||||
}
|
}
|
||||||
@@ -682,7 +682,7 @@ async fn download_icon(domain: &str) -> Result<(Bytes, Option<&str>), Error> {
|
|||||||
|
|
||||||
for icon in icon_result.iconlist.iter().take(5) {
|
for icon in icon_result.iconlist.iter().take(5) {
|
||||||
if icon.href.starts_with("data:image") {
|
if icon.href.starts_with("data:image") {
|
||||||
let datauri = DataUrl::process(&icon.href).unwrap();
|
let Ok(datauri) = DataUrl::process(&icon.href) else {continue};
|
||||||
// Check if we are able to decode the data uri
|
// Check if we are able to decode the data uri
|
||||||
let mut body = BytesMut::new();
|
let mut body = BytesMut::new();
|
||||||
match datauri.decode::<_, ()>(|bytes| {
|
match datauri.decode::<_, ()>(|bytes| {
|
||||||
@@ -891,6 +891,7 @@ impl Emitter for FaviconEmitter {
|
|||||||
FaviconToken::EndTag(ref mut tag) => {
|
FaviconToken::EndTag(ref mut tag) => {
|
||||||
// Always clean seen attributes
|
// Always clean seen attributes
|
||||||
self.seen_attributes.clear();
|
self.seen_attributes.clear();
|
||||||
|
self.set_last_start_tag(None);
|
||||||
|
|
||||||
// Only trigger an emit for the </head> tag.
|
// Only trigger an emit for the </head> tag.
|
||||||
// This is matched, and will break the for-loop.
|
// This is matched, and will break the for-loop.
|
||||||
|
@@ -14,7 +14,7 @@ use crate::{
|
|||||||
core::two_factor::{duo, email, email::EmailTokenData, yubikey},
|
core::two_factor::{duo, email, email::EmailTokenData, yubikey},
|
||||||
ApiResult, EmptyResult, JsonResult, JsonUpcase,
|
ApiResult, EmptyResult, JsonResult, JsonUpcase,
|
||||||
},
|
},
|
||||||
auth::{ClientHeaders, ClientIp},
|
auth::{generate_organization_api_key_login_claims, ClientHeaders, ClientIp},
|
||||||
db::{models::*, DbConn},
|
db::{models::*, DbConn},
|
||||||
error::MapResult,
|
error::MapResult,
|
||||||
mail, util, CONFIG,
|
mail, util, CONFIG,
|
||||||
@@ -276,16 +276,23 @@ async fn _api_key_login(
|
|||||||
conn: &mut DbConn,
|
conn: &mut DbConn,
|
||||||
ip: &ClientIp,
|
ip: &ClientIp,
|
||||||
) -> JsonResult {
|
) -> JsonResult {
|
||||||
// Validate scope
|
|
||||||
let scope = data.scope.as_ref().unwrap();
|
|
||||||
if scope != "api" {
|
|
||||||
err!("Scope not supported")
|
|
||||||
}
|
|
||||||
let scope_vec = vec!["api".into()];
|
|
||||||
|
|
||||||
// Ratelimit the login
|
// Ratelimit the login
|
||||||
crate::ratelimit::check_limit_login(&ip.ip)?;
|
crate::ratelimit::check_limit_login(&ip.ip)?;
|
||||||
|
|
||||||
|
// Validate scope
|
||||||
|
match data.scope.as_ref().unwrap().as_ref() {
|
||||||
|
"api" => _user_api_key_login(data, user_uuid, conn, ip).await,
|
||||||
|
"api.organization" => _organization_api_key_login(data, conn, ip).await,
|
||||||
|
_ => err!("Scope not supported"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn _user_api_key_login(
|
||||||
|
data: ConnectData,
|
||||||
|
user_uuid: &mut Option<String>,
|
||||||
|
conn: &mut DbConn,
|
||||||
|
ip: &ClientIp,
|
||||||
|
) -> JsonResult {
|
||||||
// Get the user via the client_id
|
// Get the user via the client_id
|
||||||
let client_id = data.client_id.as_ref().unwrap();
|
let client_id = data.client_id.as_ref().unwrap();
|
||||||
let client_user_uuid = match client_id.strip_prefix("user.") {
|
let client_user_uuid = match client_id.strip_prefix("user.") {
|
||||||
@@ -342,6 +349,7 @@ async fn _api_key_login(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Common
|
// Common
|
||||||
|
let scope_vec = vec!["api".into()];
|
||||||
let orgs = UserOrganization::find_confirmed_by_user(&user.uuid, conn).await;
|
let orgs = UserOrganization::find_confirmed_by_user(&user.uuid, conn).await;
|
||||||
let (access_token, expires_in) = device.refresh_tokens(&user, orgs, scope_vec);
|
let (access_token, expires_in) = device.refresh_tokens(&user, orgs, scope_vec);
|
||||||
device.save(conn).await?;
|
device.save(conn).await?;
|
||||||
@@ -362,13 +370,43 @@ async fn _api_key_login(
|
|||||||
"KdfMemory": user.client_kdf_memory,
|
"KdfMemory": user.client_kdf_memory,
|
||||||
"KdfParallelism": user.client_kdf_parallelism,
|
"KdfParallelism": user.client_kdf_parallelism,
|
||||||
"ResetMasterPassword": false, // TODO: Same as above
|
"ResetMasterPassword": false, // TODO: Same as above
|
||||||
"scope": scope,
|
"scope": "api",
|
||||||
"unofficialServer": true,
|
"unofficialServer": true,
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok(Json(result))
|
Ok(Json(result))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn _organization_api_key_login(data: ConnectData, conn: &mut DbConn, ip: &ClientIp) -> JsonResult {
|
||||||
|
// Get the org via the client_id
|
||||||
|
let client_id = data.client_id.as_ref().unwrap();
|
||||||
|
let org_uuid = match client_id.strip_prefix("organization.") {
|
||||||
|
Some(uuid) => uuid,
|
||||||
|
None => err!("Malformed client_id", format!("IP: {}.", ip.ip)),
|
||||||
|
};
|
||||||
|
let org_api_key = match OrganizationApiKey::find_by_org_uuid(org_uuid, conn).await {
|
||||||
|
Some(org_api_key) => org_api_key,
|
||||||
|
None => err!("Invalid client_id", format!("IP: {}.", ip.ip)),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check API key.
|
||||||
|
let client_secret = data.client_secret.as_ref().unwrap();
|
||||||
|
if !org_api_key.check_valid_api_key(client_secret) {
|
||||||
|
err!("Incorrect client_secret", format!("IP: {}. Organization: {}.", ip.ip, org_api_key.org_uuid))
|
||||||
|
}
|
||||||
|
|
||||||
|
let claim = generate_organization_api_key_login_claims(org_api_key.uuid, org_api_key.org_uuid);
|
||||||
|
let access_token = crate::auth::encode_jwt(&claim);
|
||||||
|
|
||||||
|
Ok(Json(json!({
|
||||||
|
"access_token": access_token,
|
||||||
|
"expires_in": 3600,
|
||||||
|
"token_type": "Bearer",
|
||||||
|
"scope": "api.organization",
|
||||||
|
"unofficialServer": true,
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
|
||||||
/// Retrieves an existing device or creates a new device from ConnectData and the User
|
/// Retrieves an existing device or creates a new device from ConnectData and the User
|
||||||
async fn get_device(data: &ConnectData, conn: &mut DbConn, user: &User) -> (Device, bool) {
|
async fn get_device(data: &ConnectData, conn: &mut DbConn, user: &User) -> (Device, bool) {
|
||||||
// On iOS, device_type sends "iOS", on others it sends a number
|
// On iOS, device_type sends "iOS", on others it sends a number
|
||||||
|
@@ -3,6 +3,7 @@ pub mod core;
|
|||||||
mod icons;
|
mod icons;
|
||||||
mod identity;
|
mod identity;
|
||||||
mod notifications;
|
mod notifications;
|
||||||
|
mod push;
|
||||||
mod web;
|
mod web;
|
||||||
|
|
||||||
use rocket::serde::json::Json;
|
use rocket::serde::json::Json;
|
||||||
@@ -22,6 +23,10 @@ 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},
|
||||||
|
push::{
|
||||||
|
push_cipher_update, push_folder_update, push_logout, push_send_update, push_user_update, register_push_device,
|
||||||
|
unregister_push_device,
|
||||||
|
},
|
||||||
web::catchers as web_catchers,
|
web::catchers as web_catchers,
|
||||||
web::routes as web_routes,
|
web::routes as web_routes,
|
||||||
web::static_files,
|
web::static_files,
|
||||||
|
@@ -1,16 +1,15 @@
|
|||||||
use std::{
|
use std::{
|
||||||
net::SocketAddr,
|
net::{IpAddr, SocketAddr},
|
||||||
sync::{
|
sync::Arc,
|
||||||
atomic::{AtomicBool, Ordering},
|
|
||||||
Arc,
|
|
||||||
},
|
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
use chrono::NaiveDateTime;
|
use chrono::{NaiveDateTime, Utc};
|
||||||
use futures::{SinkExt, StreamExt};
|
|
||||||
use rmpv::Value;
|
use rmpv::Value;
|
||||||
use rocket::Route;
|
use rocket::{
|
||||||
|
futures::{SinkExt, StreamExt},
|
||||||
|
Route,
|
||||||
|
};
|
||||||
use tokio::{
|
use tokio::{
|
||||||
net::{TcpListener, TcpStream},
|
net::{TcpListener, TcpStream},
|
||||||
sync::mpsc::Sender,
|
sync::mpsc::Sender,
|
||||||
@@ -21,34 +20,130 @@ use tokio_tungstenite::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
api::EmptyResult,
|
auth::ClientIp,
|
||||||
db::models::{Cipher, Folder, Send, User},
|
db::{
|
||||||
|
models::{Cipher, Folder, Send as DbSend, User},
|
||||||
|
DbConn,
|
||||||
|
},
|
||||||
Error, CONFIG,
|
Error, CONFIG,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
|
static WS_USERS: Lazy<Arc<WebSocketUsers>> = Lazy::new(|| {
|
||||||
|
Arc::new(WebSocketUsers {
|
||||||
|
map: Arc::new(dashmap::DashMap::new()),
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
use super::{push_cipher_update, push_folder_update, push_logout, push_send_update, push_user_update};
|
||||||
|
|
||||||
pub fn routes() -> Vec<Route> {
|
pub fn routes() -> Vec<Route> {
|
||||||
routes![websockets_err]
|
routes![websockets_hub]
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/hub")]
|
#[derive(FromForm, Debug)]
|
||||||
fn websockets_err() -> EmptyResult {
|
struct WsAccessToken {
|
||||||
static SHOW_WEBSOCKETS_MSG: AtomicBool = AtomicBool::new(true);
|
access_token: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
if CONFIG.websocket_enabled()
|
struct WSEntryMapGuard {
|
||||||
&& SHOW_WEBSOCKETS_MSG.compare_exchange(true, false, Ordering::Relaxed, Ordering::Relaxed).is_ok()
|
users: Arc<WebSocketUsers>,
|
||||||
{
|
user_uuid: String,
|
||||||
err!(
|
entry_uuid: uuid::Uuid,
|
||||||
"
|
addr: IpAddr,
|
||||||
###########################################################
|
}
|
||||||
'/notifications/hub' should be proxied to the websocket server or notifications won't work.
|
|
||||||
Go to the Wiki for more info, or disable WebSockets setting WEBSOCKET_ENABLED=false.
|
impl WSEntryMapGuard {
|
||||||
###########################################################################################\n"
|
fn new(users: Arc<WebSocketUsers>, user_uuid: String, entry_uuid: uuid::Uuid, addr: IpAddr) -> Self {
|
||||||
)
|
Self {
|
||||||
} else {
|
users,
|
||||||
Err(Error::empty())
|
user_uuid,
|
||||||
|
entry_uuid,
|
||||||
|
addr,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Drop for WSEntryMapGuard {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
info!("Closing WS connection from {}", self.addr);
|
||||||
|
if let Some(mut entry) = self.users.map.get_mut(&self.user_uuid) {
|
||||||
|
entry.retain(|(uuid, _)| uuid != &self.entry_uuid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/hub?<data..>")]
|
||||||
|
fn websockets_hub<'r>(
|
||||||
|
ws: rocket_ws::WebSocket,
|
||||||
|
data: WsAccessToken,
|
||||||
|
ip: ClientIp,
|
||||||
|
) -> Result<rocket_ws::Stream!['r], Error> {
|
||||||
|
let addr = ip.ip;
|
||||||
|
info!("Accepting Rocket WS connection from {addr}");
|
||||||
|
|
||||||
|
let Some(token) = data.access_token else { err_code!("Invalid claim", 401) };
|
||||||
|
let Ok(claims) = crate::auth::decode_login(&token) else { err_code!("Invalid token", 401) };
|
||||||
|
|
||||||
|
let (mut rx, guard) = {
|
||||||
|
let users = Arc::clone(&WS_USERS);
|
||||||
|
|
||||||
|
// Add a channel to send messages to this client to the map
|
||||||
|
let entry_uuid = uuid::Uuid::new_v4();
|
||||||
|
let (tx, rx) = tokio::sync::mpsc::channel::<Message>(100);
|
||||||
|
users.map.entry(claims.sub.clone()).or_default().push((entry_uuid, tx));
|
||||||
|
|
||||||
|
// Once the guard goes out of scope, the connection will have been closed and the entry will be deleted from the map
|
||||||
|
(rx, WSEntryMapGuard::new(users, claims.sub, entry_uuid, addr))
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok({
|
||||||
|
rocket_ws::Stream! { ws => {
|
||||||
|
let mut ws = ws;
|
||||||
|
let _guard = guard;
|
||||||
|
let mut interval = tokio::time::interval(Duration::from_secs(15));
|
||||||
|
loop {
|
||||||
|
tokio::select! {
|
||||||
|
res = ws.next() => {
|
||||||
|
match res {
|
||||||
|
Some(Ok(message)) => {
|
||||||
|
match message {
|
||||||
|
// Respond to any pings
|
||||||
|
Message::Ping(ping) => yield Message::Pong(ping),
|
||||||
|
Message::Pong(_) => {/* Ignored */},
|
||||||
|
|
||||||
|
// We should receive an initial message with the protocol and version, and we will reply to it
|
||||||
|
Message::Text(ref message) => {
|
||||||
|
let msg = message.strip_suffix(RECORD_SEPARATOR as char).unwrap_or(message);
|
||||||
|
|
||||||
|
if serde_json::from_str(msg).ok() == Some(INITIAL_MESSAGE) {
|
||||||
|
yield Message::binary(INITIAL_RESPONSE);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Just echo anything else the client sends
|
||||||
|
_ => yield message,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => break,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res = rx.recv() => {
|
||||||
|
match res {
|
||||||
|
Some(res) => yield res,
|
||||||
|
None => break,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = interval.tick() => yield Message::Ping(create_ping())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Websockets server
|
// Websockets server
|
||||||
//
|
//
|
||||||
@@ -127,8 +222,8 @@ impl WebSocketUsers {
|
|||||||
async fn send_update(&self, user_uuid: &str, data: &[u8]) {
|
async fn send_update(&self, user_uuid: &str, data: &[u8]) {
|
||||||
if let Some(user) = self.map.get(user_uuid).map(|v| v.clone()) {
|
if let Some(user) = self.map.get(user_uuid).map(|v| v.clone()) {
|
||||||
for (_, sender) in user.iter() {
|
for (_, sender) in user.iter() {
|
||||||
if sender.send(Message::binary(data)).await.is_err() {
|
if let Err(e) = sender.send(Message::binary(data)).await {
|
||||||
// TODO: Delete from map here too?
|
error!("Error sending WS update {e}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -143,19 +238,33 @@ impl WebSocketUsers {
|
|||||||
);
|
);
|
||||||
|
|
||||||
self.send_update(&user.uuid, &data).await;
|
self.send_update(&user.uuid, &data).await;
|
||||||
|
|
||||||
|
if CONFIG.push_enabled() {
|
||||||
|
push_user_update(ut, user);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn send_logout(&self, user: &User, acting_device_uuid: Option<String>) {
|
pub async fn send_logout(&self, user: &User, acting_device_uuid: Option<String>) {
|
||||||
let data = create_update(
|
let data = create_update(
|
||||||
vec![("UserId".into(), user.uuid.clone().into()), ("Date".into(), serialize_date(user.updated_at))],
|
vec![("UserId".into(), user.uuid.clone().into()), ("Date".into(), serialize_date(user.updated_at))],
|
||||||
UpdateType::LogOut,
|
UpdateType::LogOut,
|
||||||
acting_device_uuid,
|
acting_device_uuid.clone(),
|
||||||
);
|
);
|
||||||
|
|
||||||
self.send_update(&user.uuid, &data).await;
|
self.send_update(&user.uuid, &data).await;
|
||||||
|
|
||||||
|
if CONFIG.push_enabled() {
|
||||||
|
push_logout(user, acting_device_uuid);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn send_folder_update(&self, ut: UpdateType, folder: &Folder, acting_device_uuid: &String) {
|
pub async fn send_folder_update(
|
||||||
|
&self,
|
||||||
|
ut: UpdateType,
|
||||||
|
folder: &Folder,
|
||||||
|
acting_device_uuid: &String,
|
||||||
|
conn: &mut DbConn,
|
||||||
|
) {
|
||||||
let data = create_update(
|
let data = create_update(
|
||||||
vec![
|
vec![
|
||||||
("Id".into(), folder.uuid.clone().into()),
|
("Id".into(), folder.uuid.clone().into()),
|
||||||
@@ -167,6 +276,10 @@ impl WebSocketUsers {
|
|||||||
);
|
);
|
||||||
|
|
||||||
self.send_update(&folder.user_uuid, &data).await;
|
self.send_update(&folder.user_uuid, &data).await;
|
||||||
|
|
||||||
|
if CONFIG.push_enabled() {
|
||||||
|
push_folder_update(ut, folder, acting_device_uuid, conn).await;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn send_cipher_update(
|
pub async fn send_cipher_update(
|
||||||
@@ -175,17 +288,29 @@ impl WebSocketUsers {
|
|||||||
cipher: &Cipher,
|
cipher: &Cipher,
|
||||||
user_uuids: &[String],
|
user_uuids: &[String],
|
||||||
acting_device_uuid: &String,
|
acting_device_uuid: &String,
|
||||||
|
collection_uuids: Option<Vec<String>>,
|
||||||
|
conn: &mut DbConn,
|
||||||
) {
|
) {
|
||||||
let user_uuid = convert_option(cipher.user_uuid.clone());
|
|
||||||
let org_uuid = convert_option(cipher.organization_uuid.clone());
|
let org_uuid = convert_option(cipher.organization_uuid.clone());
|
||||||
|
// Depending if there are collections provided or not, we need to have different values for the following variables.
|
||||||
|
// The user_uuid should be `null`, and the revision date should be set to now, else the clients won't sync the collection change.
|
||||||
|
let (user_uuid, collection_uuids, revision_date) = if let Some(collection_uuids) = collection_uuids {
|
||||||
|
(
|
||||||
|
Value::Nil,
|
||||||
|
Value::Array(collection_uuids.into_iter().map(|v| v.into()).collect::<Vec<rmpv::Value>>()),
|
||||||
|
serialize_date(Utc::now().naive_utc()),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
(convert_option(cipher.user_uuid.clone()), Value::Nil, serialize_date(cipher.updated_at))
|
||||||
|
};
|
||||||
|
|
||||||
let data = create_update(
|
let data = create_update(
|
||||||
vec![
|
vec![
|
||||||
("Id".into(), cipher.uuid.clone().into()),
|
("Id".into(), cipher.uuid.clone().into()),
|
||||||
("UserId".into(), user_uuid),
|
("UserId".into(), user_uuid),
|
||||||
("OrganizationId".into(), org_uuid),
|
("OrganizationId".into(), org_uuid),
|
||||||
("CollectionIds".into(), Value::Nil),
|
("CollectionIds".into(), collection_uuids),
|
||||||
("RevisionDate".into(), serialize_date(cipher.updated_at)),
|
("RevisionDate".into(), revision_date),
|
||||||
],
|
],
|
||||||
ut,
|
ut,
|
||||||
Some(acting_device_uuid.into()),
|
Some(acting_device_uuid.into()),
|
||||||
@@ -194,9 +319,20 @@ impl WebSocketUsers {
|
|||||||
for uuid in user_uuids {
|
for uuid in user_uuids {
|
||||||
self.send_update(uuid, &data).await;
|
self.send_update(uuid, &data).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if CONFIG.push_enabled() && user_uuids.len() == 1 {
|
||||||
|
push_cipher_update(ut, cipher, acting_device_uuid, conn).await;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn send_send_update(&self, ut: UpdateType, send: &Send, user_uuids: &[String]) {
|
pub async fn send_send_update(
|
||||||
|
&self,
|
||||||
|
ut: UpdateType,
|
||||||
|
send: &DbSend,
|
||||||
|
user_uuids: &[String],
|
||||||
|
acting_device_uuid: &String,
|
||||||
|
conn: &mut DbConn,
|
||||||
|
) {
|
||||||
let user_uuid = convert_option(send.user_uuid.clone());
|
let user_uuid = convert_option(send.user_uuid.clone());
|
||||||
|
|
||||||
let data = create_update(
|
let data = create_update(
|
||||||
@@ -212,6 +348,9 @@ impl WebSocketUsers {
|
|||||||
for uuid in user_uuids {
|
for uuid in user_uuids {
|
||||||
self.send_update(uuid, &data).await;
|
self.send_update(uuid, &data).await;
|
||||||
}
|
}
|
||||||
|
if CONFIG.push_enabled() && user_uuids.len() == 1 {
|
||||||
|
push_send_update(ut, send, acting_device_uuid, conn).await;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -253,7 +392,7 @@ fn create_ping() -> Vec<u8> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
#[derive(Eq, PartialEq)]
|
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||||
pub enum UpdateType {
|
pub enum UpdateType {
|
||||||
SyncCipherUpdate = 0,
|
SyncCipherUpdate = 0,
|
||||||
SyncCipherCreate = 1,
|
SyncCipherCreate = 1,
|
||||||
@@ -280,15 +419,12 @@ pub enum UpdateType {
|
|||||||
None = 100,
|
None = 100,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Notify<'a> = &'a rocket::State<WebSocketUsers>;
|
pub type Notify<'a> = &'a rocket::State<Arc<WebSocketUsers>>;
|
||||||
|
|
||||||
pub fn start_notification_server() -> WebSocketUsers {
|
|
||||||
let users = WebSocketUsers {
|
|
||||||
map: Arc::new(dashmap::DashMap::new()),
|
|
||||||
};
|
|
||||||
|
|
||||||
|
pub fn start_notification_server() -> Arc<WebSocketUsers> {
|
||||||
|
let users = Arc::clone(&WS_USERS);
|
||||||
if CONFIG.websocket_enabled() {
|
if CONFIG.websocket_enabled() {
|
||||||
let users2 = users.clone();
|
let users2 = Arc::<WebSocketUsers>::clone(&users);
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let addr = (CONFIG.websocket_address(), CONFIG.websocket_port());
|
let addr = (CONFIG.websocket_address(), CONFIG.websocket_port());
|
||||||
info!("Starting WebSockets server on {}:{}", addr.0, addr.1);
|
info!("Starting WebSockets server on {}:{}", addr.0, addr.1);
|
||||||
@@ -300,7 +436,7 @@ pub fn start_notification_server() -> WebSocketUsers {
|
|||||||
loop {
|
loop {
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
Ok((stream, addr)) = listener.accept() => {
|
Ok((stream, addr)) = listener.accept() => {
|
||||||
tokio::spawn(handle_connection(stream, users2.clone(), addr));
|
tokio::spawn(handle_connection(stream, Arc::<WebSocketUsers>::clone(&users2), addr));
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = &mut shutdown_rx => {
|
_ = &mut shutdown_rx => {
|
||||||
@@ -316,7 +452,7 @@ pub fn start_notification_server() -> WebSocketUsers {
|
|||||||
users
|
users
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_connection(stream: TcpStream, users: WebSocketUsers, addr: SocketAddr) -> Result<(), Error> {
|
async fn handle_connection(stream: TcpStream, users: Arc<WebSocketUsers>, addr: SocketAddr) -> Result<(), Error> {
|
||||||
let mut user_uuid: Option<String> = None;
|
let mut user_uuid: Option<String> = None;
|
||||||
|
|
||||||
info!("Accepting WS connection from {addr}");
|
info!("Accepting WS connection from {addr}");
|
||||||
@@ -336,41 +472,39 @@ async fn handle_connection(stream: TcpStream, users: WebSocketUsers, addr: Socke
|
|||||||
|
|
||||||
let user_uuid = user_uuid.expect("User UUID should be set after the handshake");
|
let user_uuid = user_uuid.expect("User UUID should be set after the handshake");
|
||||||
|
|
||||||
// Add a channel to send messages to this client to the map
|
let (mut rx, guard) = {
|
||||||
let entry_uuid = uuid::Uuid::new_v4();
|
// Add a channel to send messages to this client to the map
|
||||||
let (tx, mut rx) = tokio::sync::mpsc::channel(100);
|
let entry_uuid = uuid::Uuid::new_v4();
|
||||||
users.map.entry(user_uuid.clone()).or_default().push((entry_uuid, tx));
|
let (tx, rx) = tokio::sync::mpsc::channel::<Message>(100);
|
||||||
|
users.map.entry(user_uuid.clone()).or_default().push((entry_uuid, tx));
|
||||||
|
|
||||||
|
// Once the guard goes out of scope, the connection will have been closed and the entry will be deleted from the map
|
||||||
|
(rx, WSEntryMapGuard::new(users, user_uuid, entry_uuid, addr.ip()))
|
||||||
|
};
|
||||||
|
|
||||||
|
let _guard = guard;
|
||||||
let mut interval = tokio::time::interval(Duration::from_secs(15));
|
let mut interval = tokio::time::interval(Duration::from_secs(15));
|
||||||
loop {
|
loop {
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
res = stream.next() => {
|
res = stream.next() => {
|
||||||
match res {
|
match res {
|
||||||
Some(Ok(message)) => {
|
Some(Ok(message)) => {
|
||||||
// Respond to any pings
|
match message {
|
||||||
if let Message::Ping(ping) = message {
|
// Respond to any pings
|
||||||
if stream.send(Message::Pong(ping)).await.is_err() {
|
Message::Ping(ping) => stream.send(Message::Pong(ping)).await?,
|
||||||
break;
|
Message::Pong(_) => {/* Ignored */},
|
||||||
|
|
||||||
|
// We should receive an initial message with the protocol and version, and we will reply to it
|
||||||
|
Message::Text(ref message) => {
|
||||||
|
let msg = message.strip_suffix(RECORD_SEPARATOR as char).unwrap_or(message);
|
||||||
|
|
||||||
|
if serde_json::from_str(msg).ok() == Some(INITIAL_MESSAGE) {
|
||||||
|
stream.send(Message::binary(INITIAL_RESPONSE)).await?;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
continue;
|
// Just echo anything else the client sends
|
||||||
} else if let Message::Pong(_) = message {
|
_ => stream.send(message).await?,
|
||||||
/* Ignored */
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We should receive an initial message with the protocol and version, and we will reply to it
|
|
||||||
if let Message::Text(ref message) = message {
|
|
||||||
let msg = message.strip_suffix(RECORD_SEPARATOR as char).unwrap_or(message);
|
|
||||||
|
|
||||||
if serde_json::from_str(msg).ok() == Some(INITIAL_MESSAGE) {
|
|
||||||
stream.send(Message::binary(INITIAL_RESPONSE)).await?;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Just echo anything else the client sends
|
|
||||||
if stream.send(message).await.is_err() {
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => break,
|
_ => break,
|
||||||
@@ -379,27 +513,15 @@ async fn handle_connection(stream: TcpStream, users: WebSocketUsers, addr: Socke
|
|||||||
|
|
||||||
res = rx.recv() => {
|
res = rx.recv() => {
|
||||||
match res {
|
match res {
|
||||||
Some(res) => {
|
Some(res) => stream.send(res).await?,
|
||||||
if stream.send(res).await.is_err() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
None => break,
|
None => break,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_= interval.tick() => {
|
_ = interval.tick() => stream.send(Message::Ping(create_ping())).await?
|
||||||
if stream.send(Message::Ping(create_ping())).await.is_err() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
info!("Closing WS connection from {addr}");
|
|
||||||
|
|
||||||
// Delete from map
|
|
||||||
users.map.entry(user_uuid).or_default().retain(|(uuid, _)| uuid != &entry_uuid);
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
257
src/api/push.rs
Normal file
257
src/api/push.rs
Normal file
@@ -0,0 +1,257 @@
|
|||||||
|
use reqwest::header::{ACCEPT, AUTHORIZATION, CONTENT_TYPE};
|
||||||
|
use serde_json::Value;
|
||||||
|
use tokio::sync::RwLock;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
api::{ApiResult, EmptyResult, UpdateType},
|
||||||
|
db::models::{Cipher, Device, Folder, Send, User},
|
||||||
|
util::get_reqwest_client,
|
||||||
|
CONFIG,
|
||||||
|
};
|
||||||
|
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct AuthPushToken {
|
||||||
|
access_token: String,
|
||||||
|
expires_in: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct LocalAuthPushToken {
|
||||||
|
access_token: String,
|
||||||
|
valid_until: Instant,
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_auth_push_token() -> ApiResult<String> {
|
||||||
|
static PUSH_TOKEN: Lazy<RwLock<LocalAuthPushToken>> = Lazy::new(|| {
|
||||||
|
RwLock::new(LocalAuthPushToken {
|
||||||
|
access_token: String::new(),
|
||||||
|
valid_until: Instant::now(),
|
||||||
|
})
|
||||||
|
});
|
||||||
|
let push_token = PUSH_TOKEN.read().await;
|
||||||
|
|
||||||
|
if push_token.valid_until.saturating_duration_since(Instant::now()).as_secs() > 0 {
|
||||||
|
debug!("Auth Push token still valid, no need for a new one");
|
||||||
|
return Ok(push_token.access_token.clone());
|
||||||
|
}
|
||||||
|
drop(push_token); // Drop the read lock now
|
||||||
|
|
||||||
|
let installation_id = CONFIG.push_installation_id();
|
||||||
|
let client_id = format!("installation.{installation_id}");
|
||||||
|
let client_secret = CONFIG.push_installation_key();
|
||||||
|
|
||||||
|
let params = [
|
||||||
|
("grant_type", "client_credentials"),
|
||||||
|
("scope", "api.push"),
|
||||||
|
("client_id", &client_id),
|
||||||
|
("client_secret", &client_secret),
|
||||||
|
];
|
||||||
|
|
||||||
|
let res = match get_reqwest_client().post("https://identity.bitwarden.com/connect/token").form(¶ms).send().await
|
||||||
|
{
|
||||||
|
Ok(r) => r,
|
||||||
|
Err(e) => err!(format!("Error getting push token from bitwarden server: {e}")),
|
||||||
|
};
|
||||||
|
|
||||||
|
let json_pushtoken = match res.json::<AuthPushToken>().await {
|
||||||
|
Ok(r) => r,
|
||||||
|
Err(e) => err!(format!("Unexpected push token received from bitwarden server: {e}")),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut push_token = PUSH_TOKEN.write().await;
|
||||||
|
push_token.valid_until = Instant::now()
|
||||||
|
.checked_add(Duration::new((json_pushtoken.expires_in / 2) as u64, 0)) // Token valid for half the specified time
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
push_token.access_token = json_pushtoken.access_token;
|
||||||
|
|
||||||
|
debug!("Token still valid for {}", push_token.valid_until.saturating_duration_since(Instant::now()).as_secs());
|
||||||
|
Ok(push_token.access_token.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn register_push_device(user_uuid: String, device: Device) -> EmptyResult {
|
||||||
|
if !CONFIG.push_enabled() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
let auth_push_token = get_auth_push_token().await?;
|
||||||
|
|
||||||
|
//Needed to register a device for push to bitwarden :
|
||||||
|
let data = json!({
|
||||||
|
"userId": user_uuid,
|
||||||
|
"deviceId": device.push_uuid,
|
||||||
|
"identifier": device.uuid,
|
||||||
|
"type": device.atype,
|
||||||
|
"pushToken": device.push_token
|
||||||
|
});
|
||||||
|
|
||||||
|
let auth_header = format!("Bearer {}", &auth_push_token);
|
||||||
|
|
||||||
|
get_reqwest_client()
|
||||||
|
.post(CONFIG.push_relay_uri() + "/push/register")
|
||||||
|
.header(CONTENT_TYPE, "application/json")
|
||||||
|
.header(ACCEPT, "application/json")
|
||||||
|
.header(AUTHORIZATION, auth_header)
|
||||||
|
.json(&data)
|
||||||
|
.send()
|
||||||
|
.await?
|
||||||
|
.error_for_status()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn unregister_push_device(uuid: String) -> EmptyResult {
|
||||||
|
if !CONFIG.push_enabled() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
let auth_push_token = get_auth_push_token().await?;
|
||||||
|
|
||||||
|
let auth_header = format!("Bearer {}", &auth_push_token);
|
||||||
|
|
||||||
|
match get_reqwest_client()
|
||||||
|
.delete(CONFIG.push_relay_uri() + "/push/" + &uuid)
|
||||||
|
.header(AUTHORIZATION, auth_header)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(r) => r,
|
||||||
|
Err(e) => err!(format!("An error occured during device unregistration: {e}")),
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn push_cipher_update(
|
||||||
|
ut: UpdateType,
|
||||||
|
cipher: &Cipher,
|
||||||
|
acting_device_uuid: &String,
|
||||||
|
conn: &mut crate::db::DbConn,
|
||||||
|
) {
|
||||||
|
// We shouldn't send a push notification on cipher update if the cipher belongs to an organization, this isn't implemented in the upstream server too.
|
||||||
|
if cipher.organization_uuid.is_some() {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let user_uuid = match &cipher.user_uuid {
|
||||||
|
Some(c) => c,
|
||||||
|
None => {
|
||||||
|
debug!("Cipher has no uuid");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if Device::check_user_has_push_device(user_uuid, conn).await {
|
||||||
|
send_to_push_relay(json!({
|
||||||
|
"userId": user_uuid,
|
||||||
|
"organizationId": (),
|
||||||
|
"deviceId": acting_device_uuid,
|
||||||
|
"identifier": acting_device_uuid,
|
||||||
|
"type": ut as i32,
|
||||||
|
"payload": {
|
||||||
|
"id": cipher.uuid,
|
||||||
|
"userId": cipher.user_uuid,
|
||||||
|
"organizationId": (),
|
||||||
|
"revisionDate": cipher.updated_at
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push_logout(user: &User, acting_device_uuid: Option<String>) {
|
||||||
|
let acting_device_uuid: Value = acting_device_uuid.map(|v| v.into()).unwrap_or_else(|| Value::Null);
|
||||||
|
|
||||||
|
tokio::task::spawn(send_to_push_relay(json!({
|
||||||
|
"userId": user.uuid,
|
||||||
|
"organizationId": (),
|
||||||
|
"deviceId": acting_device_uuid,
|
||||||
|
"identifier": acting_device_uuid,
|
||||||
|
"type": UpdateType::LogOut as i32,
|
||||||
|
"payload": {
|
||||||
|
"userId": user.uuid,
|
||||||
|
"date": user.updated_at
|
||||||
|
}
|
||||||
|
})));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push_user_update(ut: UpdateType, user: &User) {
|
||||||
|
tokio::task::spawn(send_to_push_relay(json!({
|
||||||
|
"userId": user.uuid,
|
||||||
|
"organizationId": (),
|
||||||
|
"deviceId": (),
|
||||||
|
"identifier": (),
|
||||||
|
"type": ut as i32,
|
||||||
|
"payload": {
|
||||||
|
"userId": user.uuid,
|
||||||
|
"date": user.updated_at
|
||||||
|
}
|
||||||
|
})));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn push_folder_update(
|
||||||
|
ut: UpdateType,
|
||||||
|
folder: &Folder,
|
||||||
|
acting_device_uuid: &String,
|
||||||
|
conn: &mut crate::db::DbConn,
|
||||||
|
) {
|
||||||
|
if Device::check_user_has_push_device(&folder.user_uuid, conn).await {
|
||||||
|
tokio::task::spawn(send_to_push_relay(json!({
|
||||||
|
"userId": folder.user_uuid,
|
||||||
|
"organizationId": (),
|
||||||
|
"deviceId": acting_device_uuid,
|
||||||
|
"identifier": acting_device_uuid,
|
||||||
|
"type": ut as i32,
|
||||||
|
"payload": {
|
||||||
|
"id": folder.uuid,
|
||||||
|
"userId": folder.user_uuid,
|
||||||
|
"revisionDate": folder.updated_at
|
||||||
|
}
|
||||||
|
})));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn push_send_update(ut: UpdateType, send: &Send, acting_device_uuid: &String, conn: &mut crate::db::DbConn) {
|
||||||
|
if let Some(s) = &send.user_uuid {
|
||||||
|
if Device::check_user_has_push_device(s, conn).await {
|
||||||
|
tokio::task::spawn(send_to_push_relay(json!({
|
||||||
|
"userId": send.user_uuid,
|
||||||
|
"organizationId": (),
|
||||||
|
"deviceId": acting_device_uuid,
|
||||||
|
"identifier": acting_device_uuid,
|
||||||
|
"type": ut as i32,
|
||||||
|
"payload": {
|
||||||
|
"id": send.uuid,
|
||||||
|
"userId": send.user_uuid,
|
||||||
|
"revisionDate": send.revision_date
|
||||||
|
}
|
||||||
|
})));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn send_to_push_relay(notification_data: Value) {
|
||||||
|
if !CONFIG.push_enabled() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let auth_push_token = match get_auth_push_token().await {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(e) => {
|
||||||
|
debug!("Could not get the auth push token: {}", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let auth_header = format!("Bearer {}", &auth_push_token);
|
||||||
|
|
||||||
|
if let Err(e) = get_reqwest_client()
|
||||||
|
.post(CONFIG.push_relay_uri() + "/push/send")
|
||||||
|
.header(ACCEPT, "application/json")
|
||||||
|
.header(CONTENT_TYPE, "application/json")
|
||||||
|
.header(AUTHORIZATION, &auth_header)
|
||||||
|
.json(¬ification_data)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
error!("An error occured while sending a send update to the push relay: {}", e);
|
||||||
|
};
|
||||||
|
}
|
@@ -5,6 +5,7 @@ use serde_json::Value;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
api::{core::now, ApiResult, EmptyResult},
|
api::{core::now, ApiResult, EmptyResult},
|
||||||
|
auth::decode_file_download,
|
||||||
error::Error,
|
error::Error,
|
||||||
util::{Cached, SafeString},
|
util::{Cached, SafeString},
|
||||||
CONFIG,
|
CONFIG,
|
||||||
@@ -91,8 +92,13 @@ async fn web_files(p: PathBuf) -> Cached<Option<NamedFile>> {
|
|||||||
Cached::long(NamedFile::open(Path::new(&CONFIG.web_vault_folder()).join(p)).await.ok(), true)
|
Cached::long(NamedFile::open(Path::new(&CONFIG.web_vault_folder()).join(p)).await.ok(), true)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/attachments/<uuid>/<file_id>")]
|
#[get("/attachments/<uuid>/<file_id>?<token>")]
|
||||||
async fn attachments(uuid: SafeString, file_id: SafeString) -> Option<NamedFile> {
|
async fn attachments(uuid: SafeString, file_id: SafeString, token: String) -> Option<NamedFile> {
|
||||||
|
let Ok(claims) = dbg!(decode_file_download(&token)) else { return None };
|
||||||
|
if claims.sub != *uuid || claims.file_id != *file_id {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
NamedFile::open(Path::new(&CONFIG.attachments_folder()).join(uuid).join(file_id)).await.ok()
|
NamedFile::open(Path::new(&CONFIG.attachments_folder()).join(uuid).join(file_id)).await.ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,8 +117,8 @@ fn alive_head(_conn: DbConn) -> EmptyResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[get("/vw_static/<filename>")]
|
#[get("/vw_static/<filename>")]
|
||||||
pub fn static_files(filename: String) -> Result<(ContentType, &'static [u8]), Error> {
|
pub fn static_files(filename: &str) -> Result<(ContentType, &'static [u8]), Error> {
|
||||||
match filename.as_ref() {
|
match filename {
|
||||||
"404.png" => Ok((ContentType::PNG, include_bytes!("../static/images/404.png"))),
|
"404.png" => Ok((ContentType::PNG, include_bytes!("../static/images/404.png"))),
|
||||||
"mail-github.png" => Ok((ContentType::PNG, include_bytes!("../static/images/mail-github.png"))),
|
"mail-github.png" => Ok((ContentType::PNG, include_bytes!("../static/images/mail-github.png"))),
|
||||||
"logo-gray.png" => Ok((ContentType::PNG, include_bytes!("../static/images/logo-gray.png"))),
|
"logo-gray.png" => Ok((ContentType::PNG, include_bytes!("../static/images/logo-gray.png"))),
|
||||||
@@ -136,8 +142,8 @@ pub fn static_files(filename: String) -> Result<(ContentType, &'static [u8]), Er
|
|||||||
"jdenticon.js" => Ok((ContentType::JavaScript, include_bytes!("../static/scripts/jdenticon.js"))),
|
"jdenticon.js" => Ok((ContentType::JavaScript, include_bytes!("../static/scripts/jdenticon.js"))),
|
||||||
"datatables.js" => Ok((ContentType::JavaScript, include_bytes!("../static/scripts/datatables.js"))),
|
"datatables.js" => Ok((ContentType::JavaScript, include_bytes!("../static/scripts/datatables.js"))),
|
||||||
"datatables.css" => Ok((ContentType::CSS, include_bytes!("../static/scripts/datatables.css"))),
|
"datatables.css" => Ok((ContentType::CSS, include_bytes!("../static/scripts/datatables.css"))),
|
||||||
"jquery-3.6.3.slim.js" => {
|
"jquery-3.6.4.slim.js" => {
|
||||||
Ok((ContentType::JavaScript, include_bytes!("../static/scripts/jquery-3.6.3.slim.js")))
|
Ok((ContentType::JavaScript, include_bytes!("../static/scripts/jquery-3.6.4.slim.js")))
|
||||||
}
|
}
|
||||||
_ => err!(format!("Static file not found: {filename}")),
|
_ => err!(format!("Static file not found: {filename}")),
|
||||||
}
|
}
|
||||||
|
123
src/auth.rs
123
src/auth.rs
@@ -23,18 +23,17 @@ static JWT_DELETE_ISSUER: Lazy<String> = Lazy::new(|| format!("{}|delete", CONFI
|
|||||||
static JWT_VERIFYEMAIL_ISSUER: Lazy<String> = Lazy::new(|| format!("{}|verifyemail", CONFIG.domain_origin()));
|
static JWT_VERIFYEMAIL_ISSUER: Lazy<String> = Lazy::new(|| format!("{}|verifyemail", CONFIG.domain_origin()));
|
||||||
static JWT_ADMIN_ISSUER: Lazy<String> = Lazy::new(|| format!("{}|admin", CONFIG.domain_origin()));
|
static JWT_ADMIN_ISSUER: Lazy<String> = Lazy::new(|| format!("{}|admin", CONFIG.domain_origin()));
|
||||||
static JWT_SEND_ISSUER: Lazy<String> = Lazy::new(|| format!("{}|send", CONFIG.domain_origin()));
|
static JWT_SEND_ISSUER: Lazy<String> = Lazy::new(|| format!("{}|send", CONFIG.domain_origin()));
|
||||||
|
static JWT_ORG_API_KEY_ISSUER: Lazy<String> = Lazy::new(|| format!("{}|api.organization", CONFIG.domain_origin()));
|
||||||
|
static JWT_FILE_DOWNLOAD_ISSUER: Lazy<String> = Lazy::new(|| format!("{}|file_download", CONFIG.domain_origin()));
|
||||||
|
|
||||||
static PRIVATE_RSA_KEY_VEC: Lazy<Vec<u8>> = Lazy::new(|| {
|
|
||||||
std::fs::read(CONFIG.private_rsa_key()).unwrap_or_else(|e| panic!("Error loading private RSA Key.\n{e}"))
|
|
||||||
});
|
|
||||||
static PRIVATE_RSA_KEY: Lazy<EncodingKey> = Lazy::new(|| {
|
static PRIVATE_RSA_KEY: Lazy<EncodingKey> = Lazy::new(|| {
|
||||||
EncodingKey::from_rsa_pem(&PRIVATE_RSA_KEY_VEC).unwrap_or_else(|e| panic!("Error decoding private RSA Key.\n{e}"))
|
let key =
|
||||||
});
|
std::fs::read(CONFIG.private_rsa_key()).unwrap_or_else(|e| panic!("Error loading private RSA Key. \n{e}"));
|
||||||
static PUBLIC_RSA_KEY_VEC: Lazy<Vec<u8>> = Lazy::new(|| {
|
EncodingKey::from_rsa_pem(&key).unwrap_or_else(|e| panic!("Error decoding private RSA Key.\n{e}"))
|
||||||
std::fs::read(CONFIG.public_rsa_key()).unwrap_or_else(|e| panic!("Error loading public RSA Key.\n{e}"))
|
|
||||||
});
|
});
|
||||||
static PUBLIC_RSA_KEY: Lazy<DecodingKey> = Lazy::new(|| {
|
static PUBLIC_RSA_KEY: Lazy<DecodingKey> = Lazy::new(|| {
|
||||||
DecodingKey::from_rsa_pem(&PUBLIC_RSA_KEY_VEC).unwrap_or_else(|e| panic!("Error decoding public RSA Key.\n{e}"))
|
let key = std::fs::read(CONFIG.public_rsa_key()).unwrap_or_else(|e| panic!("Error loading public RSA Key. \n{e}"));
|
||||||
|
DecodingKey::from_rsa_pem(&key).unwrap_or_else(|e| panic!("Error decoding public RSA Key.\n{e}"))
|
||||||
});
|
});
|
||||||
|
|
||||||
pub fn load_keys() {
|
pub fn load_keys() {
|
||||||
@@ -96,6 +95,14 @@ pub fn decode_send(token: &str) -> Result<BasicJwtClaims, Error> {
|
|||||||
decode_jwt(token, JWT_SEND_ISSUER.to_string())
|
decode_jwt(token, JWT_SEND_ISSUER.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn decode_api_org(token: &str) -> Result<OrgApiKeyLoginJwtClaims, Error> {
|
||||||
|
decode_jwt(token, JWT_ORG_API_KEY_ISSUER.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn decode_file_download(token: &str) -> Result<FileDownloadClaims, Error> {
|
||||||
|
decode_jwt(token, JWT_FILE_DOWNLOAD_ISSUER.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct LoginJwtClaims {
|
pub struct LoginJwtClaims {
|
||||||
// Not before
|
// Not before
|
||||||
@@ -203,6 +210,60 @@ pub fn generate_emergency_access_invite_claims(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct OrgApiKeyLoginJwtClaims {
|
||||||
|
// Not before
|
||||||
|
pub nbf: i64,
|
||||||
|
// Expiration time
|
||||||
|
pub exp: i64,
|
||||||
|
// Issuer
|
||||||
|
pub iss: String,
|
||||||
|
// Subject
|
||||||
|
pub sub: String,
|
||||||
|
|
||||||
|
pub client_id: String,
|
||||||
|
pub client_sub: String,
|
||||||
|
pub scope: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate_organization_api_key_login_claims(uuid: String, org_id: String) -> OrgApiKeyLoginJwtClaims {
|
||||||
|
let time_now = Utc::now().naive_utc();
|
||||||
|
OrgApiKeyLoginJwtClaims {
|
||||||
|
nbf: time_now.timestamp(),
|
||||||
|
exp: (time_now + Duration::hours(1)).timestamp(),
|
||||||
|
iss: JWT_ORG_API_KEY_ISSUER.to_string(),
|
||||||
|
sub: uuid,
|
||||||
|
client_id: format!("organization.{org_id}"),
|
||||||
|
client_sub: org_id,
|
||||||
|
scope: vec!["api.organization".into()],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct FileDownloadClaims {
|
||||||
|
// Not before
|
||||||
|
pub nbf: i64,
|
||||||
|
// Expiration time
|
||||||
|
pub exp: i64,
|
||||||
|
// Issuer
|
||||||
|
pub iss: String,
|
||||||
|
// Subject
|
||||||
|
pub sub: String,
|
||||||
|
|
||||||
|
pub file_id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate_file_download_claims(uuid: String, file_id: String) -> FileDownloadClaims {
|
||||||
|
let time_now = Utc::now().naive_utc();
|
||||||
|
FileDownloadClaims {
|
||||||
|
nbf: time_now.timestamp(),
|
||||||
|
exp: (time_now + Duration::minutes(5)).timestamp(),
|
||||||
|
iss: JWT_FILE_DOWNLOAD_ISSUER.to_string(),
|
||||||
|
sub: uuid,
|
||||||
|
file_id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct BasicJwtClaims {
|
pub struct BasicJwtClaims {
|
||||||
// Not before
|
// Not before
|
||||||
@@ -446,32 +507,34 @@ pub struct OrgHeaders {
|
|||||||
pub ip: ClientIp,
|
pub ip: ClientIp,
|
||||||
}
|
}
|
||||||
|
|
||||||
// org_id is usually the second path param ("/organizations/<org_id>"),
|
|
||||||
// but there are cases where it is a query value.
|
|
||||||
// First check the path, if this is not a valid uuid, try the query values.
|
|
||||||
fn get_org_id(request: &Request<'_>) -> Option<String> {
|
|
||||||
if let Some(Ok(org_id)) = request.param::<String>(1) {
|
|
||||||
if uuid::Uuid::parse_str(&org_id).is_ok() {
|
|
||||||
return Some(org_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(Ok(org_id)) = request.query_value::<String>("organizationId") {
|
|
||||||
if uuid::Uuid::parse_str(&org_id).is_ok() {
|
|
||||||
return Some(org_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
#[rocket::async_trait]
|
#[rocket::async_trait]
|
||||||
impl<'r> FromRequest<'r> for OrgHeaders {
|
impl<'r> FromRequest<'r> for OrgHeaders {
|
||||||
type Error = &'static str;
|
type Error = &'static str;
|
||||||
|
|
||||||
async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> {
|
async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> {
|
||||||
let headers = try_outcome!(Headers::from_request(request).await);
|
let headers = try_outcome!(Headers::from_request(request).await);
|
||||||
match get_org_id(request) {
|
|
||||||
|
// org_id is usually the second path param ("/organizations/<org_id>"),
|
||||||
|
// but there are cases where it is a query value.
|
||||||
|
// First check the path, if this is not a valid uuid, try the query values.
|
||||||
|
let url_org_id: Option<&str> = {
|
||||||
|
let mut url_org_id = None;
|
||||||
|
if let Some(Ok(org_id)) = request.param::<&str>(1) {
|
||||||
|
if uuid::Uuid::parse_str(org_id).is_ok() {
|
||||||
|
url_org_id = Some(org_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(Ok(org_id)) = request.query_value::<&str>("organizationId") {
|
||||||
|
if uuid::Uuid::parse_str(org_id).is_ok() {
|
||||||
|
url_org_id = Some(org_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
url_org_id
|
||||||
|
};
|
||||||
|
|
||||||
|
match url_org_id {
|
||||||
Some(org_id) => {
|
Some(org_id) => {
|
||||||
let mut conn = match DbConn::from_request(request).await {
|
let mut conn = match DbConn::from_request(request).await {
|
||||||
Outcome::Success(conn) => conn,
|
Outcome::Success(conn) => conn,
|
||||||
@@ -479,7 +542,7 @@ impl<'r> FromRequest<'r> for OrgHeaders {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let user = headers.user;
|
let user = headers.user;
|
||||||
let org_user = match UserOrganization::find_by_user_and_org(&user.uuid, &org_id, &mut conn).await {
|
let org_user = match UserOrganization::find_by_user_and_org(&user.uuid, org_id, &mut conn).await {
|
||||||
Some(user) => {
|
Some(user) => {
|
||||||
if user.status == UserOrgStatus::Confirmed as i32 {
|
if user.status == UserOrgStatus::Confirmed as i32 {
|
||||||
user
|
user
|
||||||
@@ -503,7 +566,7 @@ impl<'r> FromRequest<'r> for OrgHeaders {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
org_user,
|
org_user,
|
||||||
org_id,
|
org_id: String::from(org_id),
|
||||||
ip: headers.ip,
|
ip: headers.ip,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@@ -377,6 +377,16 @@ make_config! {
|
|||||||
/// Websocket port
|
/// Websocket port
|
||||||
websocket_port: u16, false, def, 3012;
|
websocket_port: u16, false, def, 3012;
|
||||||
},
|
},
|
||||||
|
push {
|
||||||
|
/// Enable push notifications
|
||||||
|
push_enabled: bool, false, def, false;
|
||||||
|
/// Push relay base uri
|
||||||
|
push_relay_uri: String, false, def, "https://push.bitwarden.com".to_string();
|
||||||
|
/// Installation id |> The installation id from https://bitwarden.com/host
|
||||||
|
push_installation_id: Pass, false, def, String::new();
|
||||||
|
/// Installation key |> The installation key from https://bitwarden.com/host
|
||||||
|
push_installation_key: Pass, false, def, String::new();
|
||||||
|
},
|
||||||
jobs {
|
jobs {
|
||||||
/// Job scheduler poll interval |> How often the job scheduler thread checks for jobs to run.
|
/// Job scheduler poll interval |> How often the job scheduler thread checks for jobs to run.
|
||||||
/// Set to 0 to globally disable scheduled jobs.
|
/// Set to 0 to globally disable scheduled jobs.
|
||||||
@@ -476,7 +486,7 @@ make_config! {
|
|||||||
/// provides unauthenticated access to potentially sensitive data.
|
/// provides unauthenticated access to potentially sensitive data.
|
||||||
show_password_hint: bool, true, def, false;
|
show_password_hint: bool, true, def, false;
|
||||||
|
|
||||||
/// Admin page token |> The token used to authenticate in this very same page. Changing it here won't deauthorize the current session
|
/// Admin token/Argon2 PHC |> The plain text token or Argon2 PHC string used to authenticate in this very same page. Changing it here will not deauthorize the current session!
|
||||||
admin_token: Pass, true, option;
|
admin_token: Pass, true, option;
|
||||||
|
|
||||||
/// Invitation organization name |> Name shown in the invitation emails that don't come from a specific organization
|
/// Invitation organization name |> Name shown in the invitation emails that don't come from a specific organization
|
||||||
@@ -603,7 +613,7 @@ make_config! {
|
|||||||
/// Global Duo settings (Note that users can override them)
|
/// Global Duo settings (Note that users can override them)
|
||||||
duo: _enable_duo {
|
duo: _enable_duo {
|
||||||
/// Enabled
|
/// Enabled
|
||||||
_enable_duo: bool, true, def, false;
|
_enable_duo: bool, true, def, true;
|
||||||
/// Integration Key
|
/// Integration Key
|
||||||
duo_ikey: String, true, option;
|
duo_ikey: String, true, option;
|
||||||
/// Secret Key
|
/// Secret Key
|
||||||
@@ -724,6 +734,17 @@ fn validate_config(cfg: &ConfigItems) -> Result<(), Error> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if cfg.push_enabled && (cfg.push_installation_id == String::new() || cfg.push_installation_key == String::new()) {
|
||||||
|
err!(
|
||||||
|
"Misconfigured Push Notification service\n\
|
||||||
|
########################################################################################\n\
|
||||||
|
# It looks like you enabled Push Notification feature, but didn't configure it #\n\
|
||||||
|
# properly. Make sure the installation id and key from https://bitwarden.com/host are #\n\
|
||||||
|
# added to your configuration. #\n\
|
||||||
|
########################################################################################\n"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if cfg._enable_duo
|
if cfg._enable_duo
|
||||||
&& (cfg.duo_host.is_some() || cfg.duo_ikey.is_some() || cfg.duo_skey.is_some())
|
&& (cfg.duo_host.is_some() || cfg.duo_ikey.is_some() || cfg.duo_skey.is_some())
|
||||||
&& !(cfg.duo_host.is_some() && cfg.duo_ikey.is_some() && cfg.duo_skey.is_some())
|
&& !(cfg.duo_host.is_some() && cfg.duo_ikey.is_some() && cfg.duo_skey.is_some())
|
||||||
|
@@ -35,7 +35,8 @@ impl Attachment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_url(&self, host: &str) -> String {
|
pub fn get_url(&self, host: &str) -> String {
|
||||||
format!("{}/attachments/{}/{}", host, self.cipher_uuid, self.id)
|
let token = encode_jwt(&generate_file_download_claims(self.cipher_uuid.clone(), self.id.clone()));
|
||||||
|
format!("{}/attachments/{}/{}?token={}", host, self.cipher_uuid, self.id, token)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_json(&self, host: &str) -> Value {
|
pub fn to_json(&self, host: &str) -> Value {
|
||||||
@@ -51,6 +52,7 @@ impl Attachment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
use crate::auth::{encode_jwt, generate_file_download_claims};
|
||||||
use crate::db::DbConn;
|
use crate::db::DbConn;
|
||||||
|
|
||||||
use crate::api::EmptyResult;
|
use crate::api::EmptyResult;
|
||||||
|
@@ -27,7 +27,8 @@ db_object! {
|
|||||||
Login = 1,
|
Login = 1,
|
||||||
SecureNote = 2,
|
SecureNote = 2,
|
||||||
Card = 3,
|
Card = 3,
|
||||||
Identity = 4
|
Identity = 4,
|
||||||
|
Fido2key = 5
|
||||||
*/
|
*/
|
||||||
pub atype: i32,
|
pub atype: i32,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
@@ -223,6 +224,7 @@ impl Cipher {
|
|||||||
"SecureNote": null,
|
"SecureNote": null,
|
||||||
"Card": null,
|
"Card": null,
|
||||||
"Identity": null,
|
"Identity": null,
|
||||||
|
"Fido2Key": null,
|
||||||
});
|
});
|
||||||
|
|
||||||
// These values are only needed for user/default syncs
|
// These values are only needed for user/default syncs
|
||||||
@@ -251,6 +253,7 @@ impl Cipher {
|
|||||||
2 => "SecureNote",
|
2 => "SecureNote",
|
||||||
3 => "Card",
|
3 => "Card",
|
||||||
4 => "Identity",
|
4 => "Identity",
|
||||||
|
5 => "Fido2Key",
|
||||||
_ => panic!("Wrong type"),
|
_ => panic!("Wrong type"),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -10,6 +10,7 @@ db_object! {
|
|||||||
pub uuid: String,
|
pub uuid: String,
|
||||||
pub org_uuid: String,
|
pub org_uuid: String,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
pub external_id: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Identifiable, Queryable, Insertable)]
|
#[derive(Identifiable, Queryable, Insertable)]
|
||||||
@@ -33,18 +34,21 @@ db_object! {
|
|||||||
|
|
||||||
/// Local methods
|
/// Local methods
|
||||||
impl Collection {
|
impl Collection {
|
||||||
pub fn new(org_uuid: String, name: String) -> Self {
|
pub fn new(org_uuid: String, name: String, external_id: Option<String>) -> Self {
|
||||||
Self {
|
let mut new_model = Self {
|
||||||
uuid: crate::util::get_uuid(),
|
uuid: crate::util::get_uuid(),
|
||||||
|
|
||||||
org_uuid,
|
org_uuid,
|
||||||
name,
|
name,
|
||||||
}
|
external_id: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
new_model.set_external_id(external_id);
|
||||||
|
new_model
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_json(&self) -> Value {
|
pub fn to_json(&self) -> Value {
|
||||||
json!({
|
json!({
|
||||||
"ExternalId": null, // Not support by us
|
"ExternalId": self.external_id,
|
||||||
"Id": self.uuid,
|
"Id": self.uuid,
|
||||||
"OrganizationId": self.org_uuid,
|
"OrganizationId": self.org_uuid,
|
||||||
"Name": self.name,
|
"Name": self.name,
|
||||||
@@ -52,6 +56,21 @@ impl Collection {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_external_id(&mut self, external_id: Option<String>) {
|
||||||
|
//Check if external id is empty. We don't want to have
|
||||||
|
//empty strings in the database
|
||||||
|
match external_id {
|
||||||
|
Some(external_id) => {
|
||||||
|
if external_id.is_empty() {
|
||||||
|
self.external_id = None;
|
||||||
|
} else {
|
||||||
|
self.external_id = Some(external_id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => self.external_id = None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn to_json_details(
|
pub async fn to_json_details(
|
||||||
&self,
|
&self,
|
||||||
user_uuid: &str,
|
user_uuid: &str,
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
use chrono::{NaiveDateTime, Utc};
|
use chrono::{NaiveDateTime, Utc};
|
||||||
|
|
||||||
use crate::CONFIG;
|
use crate::{crypto, CONFIG};
|
||||||
|
|
||||||
db_object! {
|
db_object! {
|
||||||
#[derive(Identifiable, Queryable, Insertable, AsChangeset)]
|
#[derive(Identifiable, Queryable, Insertable, AsChangeset)]
|
||||||
@@ -15,7 +15,8 @@ db_object! {
|
|||||||
pub user_uuid: String,
|
pub user_uuid: String,
|
||||||
|
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub atype: i32, // https://github.com/bitwarden/server/blob/master/src/Core/Enums/DeviceType.cs
|
pub atype: i32, // https://github.com/bitwarden/server/blob/master/src/Core/Enums/DeviceType.cs
|
||||||
|
pub push_uuid: Option<String>,
|
||||||
pub push_token: Option<String>,
|
pub push_token: Option<String>,
|
||||||
|
|
||||||
pub refresh_token: String,
|
pub refresh_token: String,
|
||||||
@@ -38,6 +39,7 @@ impl Device {
|
|||||||
name,
|
name,
|
||||||
atype,
|
atype,
|
||||||
|
|
||||||
|
push_uuid: None,
|
||||||
push_token: None,
|
push_token: None,
|
||||||
refresh_token: String::new(),
|
refresh_token: String::new(),
|
||||||
twofactor_remember: None,
|
twofactor_remember: None,
|
||||||
@@ -45,9 +47,7 @@ impl Device {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn refresh_twofactor_remember(&mut self) -> String {
|
pub fn refresh_twofactor_remember(&mut self) -> String {
|
||||||
use crate::crypto;
|
|
||||||
use data_encoding::BASE64;
|
use data_encoding::BASE64;
|
||||||
|
|
||||||
let twofactor_remember = crypto::encode_random_bytes::<180>(BASE64);
|
let twofactor_remember = crypto::encode_random_bytes::<180>(BASE64);
|
||||||
self.twofactor_remember = Some(twofactor_remember.clone());
|
self.twofactor_remember = Some(twofactor_remember.clone());
|
||||||
|
|
||||||
@@ -66,9 +66,7 @@ impl Device {
|
|||||||
) -> (String, i64) {
|
) -> (String, i64) {
|
||||||
// If there is no refresh token, we create one
|
// If there is no refresh token, we create one
|
||||||
if self.refresh_token.is_empty() {
|
if self.refresh_token.is_empty() {
|
||||||
use crate::crypto;
|
|
||||||
use data_encoding::BASE64URL;
|
use data_encoding::BASE64URL;
|
||||||
|
|
||||||
self.refresh_token = crypto::encode_random_bytes::<64>(BASE64URL);
|
self.refresh_token = crypto::encode_random_bytes::<64>(BASE64URL);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,6 +153,35 @@ impl Device {
|
|||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn find_by_user(user_uuid: &str, conn: &mut DbConn) -> Vec<Self> {
|
||||||
|
db_run! { conn: {
|
||||||
|
devices::table
|
||||||
|
.filter(devices::user_uuid.eq(user_uuid))
|
||||||
|
.load::<DeviceDb>(conn)
|
||||||
|
.expect("Error loading devices")
|
||||||
|
.from_db()
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn find_by_uuid(uuid: &str, conn: &mut DbConn) -> Option<Self> {
|
||||||
|
db_run! { conn: {
|
||||||
|
devices::table
|
||||||
|
.filter(devices::uuid.eq(uuid))
|
||||||
|
.first::<DeviceDb>(conn)
|
||||||
|
.ok()
|
||||||
|
.from_db()
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn clear_push_token_by_uuid(uuid: &str, conn: &mut DbConn) -> EmptyResult {
|
||||||
|
db_run! { conn: {
|
||||||
|
diesel::update(devices::table)
|
||||||
|
.filter(devices::uuid.eq(uuid))
|
||||||
|
.set(devices::push_token.eq::<Option<String>>(None))
|
||||||
|
.execute(conn)
|
||||||
|
.map_res("Error removing push token")
|
||||||
|
}}
|
||||||
|
}
|
||||||
pub async fn find_by_refresh_token(refresh_token: &str, conn: &mut DbConn) -> Option<Self> {
|
pub async fn find_by_refresh_token(refresh_token: &str, conn: &mut DbConn) -> Option<Self> {
|
||||||
db_run! { conn: {
|
db_run! { conn: {
|
||||||
devices::table
|
devices::table
|
||||||
@@ -175,4 +202,26 @@ impl Device {
|
|||||||
.from_db()
|
.from_db()
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
pub async fn find_push_devices_by_user(user_uuid: &str, conn: &mut DbConn) -> Vec<Self> {
|
||||||
|
db_run! { conn: {
|
||||||
|
devices::table
|
||||||
|
.filter(devices::user_uuid.eq(user_uuid))
|
||||||
|
.filter(devices::push_token.is_not_null())
|
||||||
|
.load::<DeviceDb>(conn)
|
||||||
|
.expect("Error loading push devices")
|
||||||
|
.from_db()
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn check_user_has_push_device(user_uuid: &str, conn: &mut DbConn) -> bool {
|
||||||
|
db_run! { conn: {
|
||||||
|
devices::table
|
||||||
|
.filter(devices::user_uuid.eq(user_uuid))
|
||||||
|
.filter(devices::push_token.is_not_null())
|
||||||
|
.count()
|
||||||
|
.first::<i64>(conn)
|
||||||
|
.ok()
|
||||||
|
.unwrap_or(0) != 0
|
||||||
|
}}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -10,7 +10,7 @@ db_object! {
|
|||||||
pub organizations_uuid: String,
|
pub organizations_uuid: String,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub access_all: bool,
|
pub access_all: bool,
|
||||||
external_id: Option<String>,
|
pub external_id: Option<String>,
|
||||||
pub creation_date: NaiveDateTime,
|
pub creation_date: NaiveDateTime,
|
||||||
pub revision_date: NaiveDateTime,
|
pub revision_date: NaiveDateTime,
|
||||||
}
|
}
|
||||||
@@ -107,10 +107,6 @@ impl Group {
|
|||||||
None => self.external_id = None,
|
None => self.external_id = None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_external_id(&self) -> Option<String> {
|
|
||||||
self.external_id.clone()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CollectionGroup {
|
impl CollectionGroup {
|
||||||
@@ -214,6 +210,15 @@ impl Group {
|
|||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn find_by_external_id(id: &str, conn: &mut DbConn) -> Option<Self> {
|
||||||
|
db_run! { conn: {
|
||||||
|
groups::table
|
||||||
|
.filter(groups::external_id.eq(id))
|
||||||
|
.first::<GroupDb>(conn)
|
||||||
|
.ok()
|
||||||
|
.from_db()
|
||||||
|
}}
|
||||||
|
}
|
||||||
//Returns all organizations the user has full access to
|
//Returns all organizations the user has full access to
|
||||||
pub async fn gather_user_organizations_full_access(user_uuid: &str, conn: &mut DbConn) -> Vec<String> {
|
pub async fn gather_user_organizations_full_access(user_uuid: &str, conn: &mut DbConn) -> Vec<String> {
|
||||||
db_run! { conn: {
|
db_run! { conn: {
|
||||||
|
@@ -24,7 +24,7 @@ pub use self::favorite::Favorite;
|
|||||||
pub use self::folder::{Folder, FolderCipher};
|
pub use self::folder::{Folder, FolderCipher};
|
||||||
pub use self::group::{CollectionGroup, Group, GroupUser};
|
pub use self::group::{CollectionGroup, Group, GroupUser};
|
||||||
pub use self::org_policy::{OrgPolicy, OrgPolicyErr, OrgPolicyType};
|
pub use self::org_policy::{OrgPolicy, OrgPolicyErr, OrgPolicyType};
|
||||||
pub use self::organization::{Organization, UserOrgStatus, UserOrgType, UserOrganization};
|
pub use self::organization::{Organization, OrganizationApiKey, 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};
|
||||||
pub use self::two_factor_incomplete::TwoFactorIncomplete;
|
pub use self::two_factor_incomplete::TwoFactorIncomplete;
|
||||||
|
@@ -309,7 +309,7 @@ impl OrgPolicy {
|
|||||||
match OrgPolicy::find_by_org_and_type(org_uuid, OrgPolicyType::ResetPassword, conn).await {
|
match OrgPolicy::find_by_org_and_type(org_uuid, OrgPolicyType::ResetPassword, conn).await {
|
||||||
Some(policy) => match serde_json::from_str::<UpCase<ResetPasswordDataModel>>(&policy.data) {
|
Some(policy) => match serde_json::from_str::<UpCase<ResetPasswordDataModel>>(&policy.data) {
|
||||||
Ok(opts) => {
|
Ok(opts) => {
|
||||||
return opts.data.AutoEnrollEnabled;
|
return policy.enabled && opts.data.AutoEnrollEnabled;
|
||||||
}
|
}
|
||||||
_ => error!("Failed to deserialize ResetPasswordDataModel: {}", policy.data),
|
_ => error!("Failed to deserialize ResetPasswordDataModel: {}", policy.data),
|
||||||
},
|
},
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
use chrono::{NaiveDateTime, Utc};
|
||||||
use num_traits::FromPrimitive;
|
use num_traits::FromPrimitive;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
@@ -31,6 +32,17 @@ db_object! {
|
|||||||
pub atype: i32,
|
pub atype: i32,
|
||||||
pub reset_password_key: Option<String>,
|
pub reset_password_key: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Identifiable, Queryable, Insertable, AsChangeset)]
|
||||||
|
#[diesel(table_name = organization_api_key)]
|
||||||
|
#[diesel(primary_key(uuid, org_uuid))]
|
||||||
|
pub struct OrganizationApiKey {
|
||||||
|
pub uuid: String,
|
||||||
|
pub org_uuid: String,
|
||||||
|
pub atype: i32,
|
||||||
|
pub api_key: String,
|
||||||
|
pub revision_date: NaiveDateTime,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://github.com/bitwarden/server/blob/b86a04cef9f1e1b82cf18e49fc94e017c641130c/src/Core/Enums/OrganizationUserStatusType.cs
|
// https://github.com/bitwarden/server/blob/b86a04cef9f1e1b82cf18e49fc94e017c641130c/src/Core/Enums/OrganizationUserStatusType.cs
|
||||||
@@ -157,7 +169,7 @@ impl Organization {
|
|||||||
"UseSso": false, // Not supported
|
"UseSso": false, // Not supported
|
||||||
// "UseKeyConnector": false, // Not supported
|
// "UseKeyConnector": false, // Not supported
|
||||||
"SelfHost": true,
|
"SelfHost": true,
|
||||||
"UseApi": false, // Not supported
|
"UseApi": true,
|
||||||
"HasPublicAndPrivateKeys": self.private_key.is_some() && self.public_key.is_some(),
|
"HasPublicAndPrivateKeys": self.private_key.is_some() && self.public_key.is_some(),
|
||||||
"UseResetPassword": CONFIG.mail_enabled(),
|
"UseResetPassword": CONFIG.mail_enabled(),
|
||||||
|
|
||||||
@@ -212,6 +224,23 @@ impl UserOrganization {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl OrganizationApiKey {
|
||||||
|
pub fn new(org_uuid: String, api_key: String) -> Self {
|
||||||
|
Self {
|
||||||
|
uuid: crate::util::get_uuid(),
|
||||||
|
|
||||||
|
org_uuid,
|
||||||
|
atype: 0, // Type 0 is the default and only type we support currently
|
||||||
|
api_key,
|
||||||
|
revision_date: Utc::now().naive_utc(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn check_valid_api_key(&self, api_key: &str) -> bool {
|
||||||
|
crate::crypto::ct_eq(&self.api_key, api_key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
use crate::db::DbConn;
|
use crate::db::DbConn;
|
||||||
|
|
||||||
use crate::api::EmptyResult;
|
use crate::api::EmptyResult;
|
||||||
@@ -311,7 +340,7 @@ impl UserOrganization {
|
|||||||
"UseTotp": true,
|
"UseTotp": true,
|
||||||
// "UseScim": false, // Not supported (Not AGPLv3 Licensed)
|
// "UseScim": false, // Not supported (Not AGPLv3 Licensed)
|
||||||
"UsePolicies": true,
|
"UsePolicies": true,
|
||||||
"UseApi": false, // Not supported
|
"UseApi": true,
|
||||||
"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": self.reset_password_key.is_some(),
|
"ResetPasswordEnrolled": self.reset_password_key.is_some(),
|
||||||
@@ -481,7 +510,7 @@ impl UserOrganization {
|
|||||||
.set(UserOrganizationDb::to_db(self))
|
.set(UserOrganizationDb::to_db(self))
|
||||||
.execute(conn)
|
.execute(conn)
|
||||||
.map_res("Error adding user to organization")
|
.map_res("Error adding user to organization")
|
||||||
}
|
},
|
||||||
Err(e) => Err(e.into()),
|
Err(e) => Err(e.into()),
|
||||||
}.map_res("Error adding user to organization")
|
}.map_res("Error adding user to organization")
|
||||||
}
|
}
|
||||||
@@ -750,6 +779,50 @@ impl UserOrganization {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl OrganizationApiKey {
|
||||||
|
pub async fn save(&self, conn: &DbConn) -> EmptyResult {
|
||||||
|
db_run! { conn:
|
||||||
|
sqlite, mysql {
|
||||||
|
match diesel::replace_into(organization_api_key::table)
|
||||||
|
.values(OrganizationApiKeyDb::to_db(self))
|
||||||
|
.execute(conn)
|
||||||
|
{
|
||||||
|
Ok(_) => Ok(()),
|
||||||
|
// Record already exists and causes a Foreign Key Violation because replace_into() wants to delete the record first.
|
||||||
|
Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => {
|
||||||
|
diesel::update(organization_api_key::table)
|
||||||
|
.filter(organization_api_key::uuid.eq(&self.uuid))
|
||||||
|
.set(OrganizationApiKeyDb::to_db(self))
|
||||||
|
.execute(conn)
|
||||||
|
.map_res("Error saving organization")
|
||||||
|
}
|
||||||
|
Err(e) => Err(e.into()),
|
||||||
|
}.map_res("Error saving organization")
|
||||||
|
|
||||||
|
}
|
||||||
|
postgresql {
|
||||||
|
let value = OrganizationApiKeyDb::to_db(self);
|
||||||
|
diesel::insert_into(organization_api_key::table)
|
||||||
|
.values(&value)
|
||||||
|
.on_conflict(organization_api_key::uuid)
|
||||||
|
.do_update()
|
||||||
|
.set(&value)
|
||||||
|
.execute(conn)
|
||||||
|
.map_res("Error saving organization")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn find_by_org_uuid(org_uuid: &str, conn: &DbConn) -> Option<Self> {
|
||||||
|
db_run! { conn: {
|
||||||
|
organization_api_key::table
|
||||||
|
.filter(organization_api_key::org_uuid.eq(org_uuid))
|
||||||
|
.first::<OrganizationApiKeyDb>(conn)
|
||||||
|
.ok().from_db()
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@@ -50,6 +50,8 @@ db_object! {
|
|||||||
pub api_key: Option<String>,
|
pub api_key: Option<String>,
|
||||||
|
|
||||||
pub avatar_color: Option<String>,
|
pub avatar_color: Option<String>,
|
||||||
|
|
||||||
|
pub external_id: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Identifiable, Queryable, Insertable)]
|
#[derive(Identifiable, Queryable, Insertable)]
|
||||||
@@ -126,6 +128,8 @@ impl User {
|
|||||||
api_key: None,
|
api_key: None,
|
||||||
|
|
||||||
avatar_color: None,
|
avatar_color: None,
|
||||||
|
|
||||||
|
external_id: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,6 +154,18 @@ impl User {
|
|||||||
matches!(self.api_key, Some(ref api_key) if crate::crypto::ct_eq(api_key, key))
|
matches!(self.api_key, Some(ref api_key) if crate::crypto::ct_eq(api_key, key))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_external_id(&mut self, external_id: Option<String>) {
|
||||||
|
//Check if external id is empty. We don't want to have
|
||||||
|
//empty strings in the database
|
||||||
|
let mut ext_id: Option<String> = None;
|
||||||
|
if let Some(external_id) = external_id {
|
||||||
|
if !external_id.is_empty() {
|
||||||
|
ext_id = Some(external_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.external_id = ext_id;
|
||||||
|
}
|
||||||
|
|
||||||
/// Set the password hash generated
|
/// Set the password hash generated
|
||||||
/// And resets the security_stamp. Based upon the allow_next_route the security_stamp will be different.
|
/// And resets the security_stamp. Based upon the allow_next_route the security_stamp will be different.
|
||||||
///
|
///
|
||||||
@@ -376,6 +392,12 @@ impl User {
|
|||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn find_by_external_id(id: &str, conn: &mut DbConn) -> Option<Self> {
|
||||||
|
db_run! {conn: {
|
||||||
|
users::table.filter(users::external_id.eq(id)).first::<UserDb>(conn).ok().from_db()
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn get_all(conn: &mut DbConn) -> Vec<Self> {
|
pub async fn get_all(conn: &mut DbConn) -> Vec<Self> {
|
||||||
db_run! {conn: {
|
db_run! {conn: {
|
||||||
users::table.load::<UserDb>(conn).expect("Error loading users").from_db()
|
users::table.load::<UserDb>(conn).expect("Error loading users").from_db()
|
||||||
|
@@ -38,6 +38,7 @@ table! {
|
|||||||
uuid -> Text,
|
uuid -> Text,
|
||||||
org_uuid -> Text,
|
org_uuid -> Text,
|
||||||
name -> Text,
|
name -> Text,
|
||||||
|
external_id -> Nullable<Text>,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,6 +50,7 @@ table! {
|
|||||||
user_uuid -> Text,
|
user_uuid -> Text,
|
||||||
name -> Text,
|
name -> Text,
|
||||||
atype -> Integer,
|
atype -> Integer,
|
||||||
|
push_uuid -> Nullable<Text>,
|
||||||
push_token -> Nullable<Text>,
|
push_token -> Nullable<Text>,
|
||||||
refresh_token -> Text,
|
refresh_token -> Text,
|
||||||
twofactor_remember -> Nullable<Text>,
|
twofactor_remember -> Nullable<Text>,
|
||||||
@@ -203,6 +205,7 @@ table! {
|
|||||||
client_kdf_parallelism -> Nullable<Integer>,
|
client_kdf_parallelism -> Nullable<Integer>,
|
||||||
api_key -> Nullable<Text>,
|
api_key -> Nullable<Text>,
|
||||||
avatar_color -> Nullable<Text>,
|
avatar_color -> Nullable<Text>,
|
||||||
|
external_id -> Nullable<Text>,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -228,6 +231,16 @@ table! {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
table! {
|
||||||
|
organization_api_key (uuid, org_uuid) {
|
||||||
|
uuid -> Text,
|
||||||
|
org_uuid -> Text,
|
||||||
|
atype -> Integer,
|
||||||
|
api_key -> Text,
|
||||||
|
revision_date -> Timestamp,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
table! {
|
table! {
|
||||||
emergency_access (uuid) {
|
emergency_access (uuid) {
|
||||||
uuid -> Text,
|
uuid -> Text,
|
||||||
@@ -291,6 +304,7 @@ joinable!(users_collections -> collections (collection_uuid));
|
|||||||
joinable!(users_collections -> users (user_uuid));
|
joinable!(users_collections -> users (user_uuid));
|
||||||
joinable!(users_organizations -> organizations (org_uuid));
|
joinable!(users_organizations -> organizations (org_uuid));
|
||||||
joinable!(users_organizations -> users (user_uuid));
|
joinable!(users_organizations -> users (user_uuid));
|
||||||
|
joinable!(organization_api_key -> organizations (org_uuid));
|
||||||
joinable!(emergency_access -> users (grantor_uuid));
|
joinable!(emergency_access -> users (grantor_uuid));
|
||||||
joinable!(groups -> organizations (organizations_uuid));
|
joinable!(groups -> organizations (organizations_uuid));
|
||||||
joinable!(groups_users -> users_organizations (users_organizations_uuid));
|
joinable!(groups_users -> users_organizations (users_organizations_uuid));
|
||||||
@@ -315,6 +329,7 @@ allow_tables_to_appear_in_same_query!(
|
|||||||
users,
|
users,
|
||||||
users_collections,
|
users_collections,
|
||||||
users_organizations,
|
users_organizations,
|
||||||
|
organization_api_key,
|
||||||
emergency_access,
|
emergency_access,
|
||||||
groups,
|
groups,
|
||||||
groups_users,
|
groups_users,
|
||||||
|
@@ -38,6 +38,7 @@ table! {
|
|||||||
uuid -> Text,
|
uuid -> Text,
|
||||||
org_uuid -> Text,
|
org_uuid -> Text,
|
||||||
name -> Text,
|
name -> Text,
|
||||||
|
external_id -> Nullable<Text>,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,6 +50,7 @@ table! {
|
|||||||
user_uuid -> Text,
|
user_uuid -> Text,
|
||||||
name -> Text,
|
name -> Text,
|
||||||
atype -> Integer,
|
atype -> Integer,
|
||||||
|
push_uuid -> Nullable<Text>,
|
||||||
push_token -> Nullable<Text>,
|
push_token -> Nullable<Text>,
|
||||||
refresh_token -> Text,
|
refresh_token -> Text,
|
||||||
twofactor_remember -> Nullable<Text>,
|
twofactor_remember -> Nullable<Text>,
|
||||||
@@ -203,6 +205,7 @@ table! {
|
|||||||
client_kdf_parallelism -> Nullable<Integer>,
|
client_kdf_parallelism -> Nullable<Integer>,
|
||||||
api_key -> Nullable<Text>,
|
api_key -> Nullable<Text>,
|
||||||
avatar_color -> Nullable<Text>,
|
avatar_color -> Nullable<Text>,
|
||||||
|
external_id -> Nullable<Text>,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -228,6 +231,16 @@ table! {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
table! {
|
||||||
|
organization_api_key (uuid, org_uuid) {
|
||||||
|
uuid -> Text,
|
||||||
|
org_uuid -> Text,
|
||||||
|
atype -> Integer,
|
||||||
|
api_key -> Text,
|
||||||
|
revision_date -> Timestamp,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
table! {
|
table! {
|
||||||
emergency_access (uuid) {
|
emergency_access (uuid) {
|
||||||
uuid -> Text,
|
uuid -> Text,
|
||||||
@@ -291,6 +304,7 @@ joinable!(users_collections -> collections (collection_uuid));
|
|||||||
joinable!(users_collections -> users (user_uuid));
|
joinable!(users_collections -> users (user_uuid));
|
||||||
joinable!(users_organizations -> organizations (org_uuid));
|
joinable!(users_organizations -> organizations (org_uuid));
|
||||||
joinable!(users_organizations -> users (user_uuid));
|
joinable!(users_organizations -> users (user_uuid));
|
||||||
|
joinable!(organization_api_key -> organizations (org_uuid));
|
||||||
joinable!(emergency_access -> users (grantor_uuid));
|
joinable!(emergency_access -> users (grantor_uuid));
|
||||||
joinable!(groups -> organizations (organizations_uuid));
|
joinable!(groups -> organizations (organizations_uuid));
|
||||||
joinable!(groups_users -> users_organizations (users_organizations_uuid));
|
joinable!(groups_users -> users_organizations (users_organizations_uuid));
|
||||||
@@ -315,6 +329,7 @@ allow_tables_to_appear_in_same_query!(
|
|||||||
users,
|
users,
|
||||||
users_collections,
|
users_collections,
|
||||||
users_organizations,
|
users_organizations,
|
||||||
|
organization_api_key,
|
||||||
emergency_access,
|
emergency_access,
|
||||||
groups,
|
groups,
|
||||||
groups_users,
|
groups_users,
|
||||||
|
@@ -38,6 +38,7 @@ table! {
|
|||||||
uuid -> Text,
|
uuid -> Text,
|
||||||
org_uuid -> Text,
|
org_uuid -> Text,
|
||||||
name -> Text,
|
name -> Text,
|
||||||
|
external_id -> Nullable<Text>,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,6 +50,7 @@ table! {
|
|||||||
user_uuid -> Text,
|
user_uuid -> Text,
|
||||||
name -> Text,
|
name -> Text,
|
||||||
atype -> Integer,
|
atype -> Integer,
|
||||||
|
push_uuid -> Nullable<Text>,
|
||||||
push_token -> Nullable<Text>,
|
push_token -> Nullable<Text>,
|
||||||
refresh_token -> Text,
|
refresh_token -> Text,
|
||||||
twofactor_remember -> Nullable<Text>,
|
twofactor_remember -> Nullable<Text>,
|
||||||
@@ -203,6 +205,7 @@ table! {
|
|||||||
client_kdf_parallelism -> Nullable<Integer>,
|
client_kdf_parallelism -> Nullable<Integer>,
|
||||||
api_key -> Nullable<Text>,
|
api_key -> Nullable<Text>,
|
||||||
avatar_color -> Nullable<Text>,
|
avatar_color -> Nullable<Text>,
|
||||||
|
external_id -> Nullable<Text>,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -228,6 +231,16 @@ table! {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
table! {
|
||||||
|
organization_api_key (uuid, org_uuid) {
|
||||||
|
uuid -> Text,
|
||||||
|
org_uuid -> Text,
|
||||||
|
atype -> Integer,
|
||||||
|
api_key -> Text,
|
||||||
|
revision_date -> Timestamp,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
table! {
|
table! {
|
||||||
emergency_access (uuid) {
|
emergency_access (uuid) {
|
||||||
uuid -> Text,
|
uuid -> Text,
|
||||||
@@ -292,6 +305,7 @@ joinable!(users_collections -> users (user_uuid));
|
|||||||
joinable!(users_organizations -> organizations (org_uuid));
|
joinable!(users_organizations -> organizations (org_uuid));
|
||||||
joinable!(users_organizations -> users (user_uuid));
|
joinable!(users_organizations -> users (user_uuid));
|
||||||
joinable!(users_organizations -> ciphers (org_uuid));
|
joinable!(users_organizations -> ciphers (org_uuid));
|
||||||
|
joinable!(organization_api_key -> organizations (org_uuid));
|
||||||
joinable!(emergency_access -> users (grantor_uuid));
|
joinable!(emergency_access -> users (grantor_uuid));
|
||||||
joinable!(groups -> organizations (organizations_uuid));
|
joinable!(groups -> organizations (organizations_uuid));
|
||||||
joinable!(groups_users -> users_organizations (users_organizations_uuid));
|
joinable!(groups_users -> users_organizations (users_organizations_uuid));
|
||||||
@@ -316,6 +330,7 @@ allow_tables_to_appear_in_same_query!(
|
|||||||
users,
|
users,
|
||||||
users_collections,
|
users_collections,
|
||||||
users_organizations,
|
users_organizations,
|
||||||
|
organization_api_key,
|
||||||
emergency_access,
|
emergency_access,
|
||||||
groups,
|
groups,
|
||||||
groups_users,
|
groups_users,
|
||||||
|
@@ -573,8 +573,8 @@ async fn send_email(address: &str, subject: &str, body_html: String, body_text:
|
|||||||
let smtp_from = &CONFIG.smtp_from();
|
let smtp_from = &CONFIG.smtp_from();
|
||||||
|
|
||||||
let body = if CONFIG.smtp_embed_images() {
|
let body = if CONFIG.smtp_embed_images() {
|
||||||
let logo_gray_body = Body::new(crate::api::static_files("logo-gray.png".to_string()).unwrap().1.to_vec());
|
let logo_gray_body = Body::new(crate::api::static_files("logo-gray.png").unwrap().1.to_vec());
|
||||||
let mail_github_body = Body::new(crate::api::static_files("mail-github.png".to_string()).unwrap().1.to_vec());
|
let mail_github_body = Body::new(crate::api::static_files("mail-github.png").unwrap().1.to_vec());
|
||||||
MultiPart::alternative().singlepart(SinglePart::plain(body_text)).multipart(
|
MultiPart::alternative().singlepart(SinglePart::plain(body_text)).multipart(
|
||||||
MultiPart::related()
|
MultiPart::related()
|
||||||
.singlepart(SinglePart::html(body_html))
|
.singlepart(SinglePart::html(body_html))
|
||||||
|
@@ -250,7 +250,7 @@ fn init_logging(level: log::LevelFilter) -> Result<(), fern::InitError> {
|
|||||||
log::LevelFilter::Off
|
log::LevelFilter::Off
|
||||||
};
|
};
|
||||||
|
|
||||||
// Only show rocket underscore `_` logs when the level is Debug or higher
|
// Only show Rocket underscore `_` logs when the level is Debug or higher
|
||||||
// Else this will bloat the log output with useless messages.
|
// Else this will bloat the log output with useless messages.
|
||||||
let rocket_underscore_level = if level >= log::LevelFilter::Debug {
|
let rocket_underscore_level = if level >= log::LevelFilter::Debug {
|
||||||
log::LevelFilter::Warn
|
log::LevelFilter::Warn
|
||||||
@@ -264,8 +264,13 @@ fn init_logging(level: log::LevelFilter) -> Result<(), fern::InitError> {
|
|||||||
.level_for("rustls::session", log::LevelFilter::Off)
|
.level_for("rustls::session", log::LevelFilter::Off)
|
||||||
// Hide failed to close stream messages
|
// Hide failed to close stream messages
|
||||||
.level_for("hyper::server", log::LevelFilter::Warn)
|
.level_for("hyper::server", log::LevelFilter::Warn)
|
||||||
// Silence rocket logs
|
// Silence Rocket `_` logs
|
||||||
.level_for("_", rocket_underscore_level)
|
.level_for("_", rocket_underscore_level)
|
||||||
|
.level_for("rocket::response::responder::_", rocket_underscore_level)
|
||||||
|
.level_for("rocket::server::_", rocket_underscore_level)
|
||||||
|
.level_for("vaultwarden::api::admin::_", rocket_underscore_level)
|
||||||
|
.level_for("vaultwarden::api::notifications::_", rocket_underscore_level)
|
||||||
|
// Silence Rocket logs
|
||||||
.level_for("rocket::launch", log::LevelFilter::Error)
|
.level_for("rocket::launch", log::LevelFilter::Error)
|
||||||
.level_for("rocket::launch_", log::LevelFilter::Error)
|
.level_for("rocket::launch_", log::LevelFilter::Error)
|
||||||
.level_for("rocket::rocket", log::LevelFilter::Warn)
|
.level_for("rocket::rocket", log::LevelFilter::Warn)
|
||||||
|
@@ -952,5 +952,24 @@
|
|||||||
"jira.com"
|
"jira.com"
|
||||||
],
|
],
|
||||||
"Excluded": false
|
"Excluded": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Type": 90,
|
||||||
|
"Domains": [
|
||||||
|
"pinterest.com",
|
||||||
|
"pinterest.com.au",
|
||||||
|
"pinterest.cl",
|
||||||
|
"pinterest.de",
|
||||||
|
"pinterest.dk",
|
||||||
|
"pinterest.es",
|
||||||
|
"pinterest.fr",
|
||||||
|
"pinterest.co.uk",
|
||||||
|
"pinterest.jp",
|
||||||
|
"pinterest.co.kr",
|
||||||
|
"pinterest.nz",
|
||||||
|
"pinterest.pt",
|
||||||
|
"pinterest.se"
|
||||||
|
],
|
||||||
|
"Excluded": false
|
||||||
}
|
}
|
||||||
]
|
]
|
4
src/static/scripts/admin.css
vendored
4
src/static/scripts/admin.css
vendored
@@ -25,7 +25,7 @@ img {
|
|||||||
min-width: 85px;
|
min-width: 85px;
|
||||||
max-width: 85px;
|
max-width: 85px;
|
||||||
}
|
}
|
||||||
#users-table .vw-ciphers, #orgs-table .vw-users, #orgs-table .vw-ciphers {
|
#users-table .vw-entries, #orgs-table .vw-users, #orgs-table .vw-entries {
|
||||||
min-width: 35px;
|
min-width: 35px;
|
||||||
max-width: 40px;
|
max-width: 40px;
|
||||||
}
|
}
|
||||||
@@ -53,4 +53,4 @@ img {
|
|||||||
}
|
}
|
||||||
.vw-copy-toast {
|
.vw-copy-toast {
|
||||||
width: 15rem;
|
width: 15rem;
|
||||||
}
|
}
|
||||||
|
13
src/static/scripts/admin.js
vendored
13
src/static/scripts/admin.js
vendored
@@ -3,16 +3,17 @@
|
|||||||
/* exported BASE_URL, _post */
|
/* exported BASE_URL, _post */
|
||||||
|
|
||||||
function getBaseUrl() {
|
function getBaseUrl() {
|
||||||
// If the base URL is `https://vaultwarden.example.com/base/path/`,
|
// If the base URL is `https://vaultwarden.example.com/base/path/admin/`,
|
||||||
// `window.location.href` should have one of the following forms:
|
// `window.location.href` should have one of the following forms:
|
||||||
//
|
//
|
||||||
// - `https://vaultwarden.example.com/base/path/`
|
// - `https://vaultwarden.example.com/base/path/admin`
|
||||||
// - `https://vaultwarden.example.com/base/path/#/some/route[?queryParam=...]`
|
// - `https://vaultwarden.example.com/base/path/admin/#/some/route[?queryParam=...]`
|
||||||
//
|
//
|
||||||
// We want to get to just `https://vaultwarden.example.com/base/path`.
|
// We want to get to just `https://vaultwarden.example.com/base/path`.
|
||||||
const baseUrl = window.location.href;
|
const pathname = window.location.pathname;
|
||||||
const adminPos = baseUrl.indexOf("/admin");
|
const adminPos = pathname.indexOf("/admin");
|
||||||
return baseUrl.substring(0, adminPos != -1 ? adminPos : baseUrl.length);
|
const newPathname = pathname.substring(0, adminPos != -1 ? adminPos : pathname.length);
|
||||||
|
return `${window.location.origin}${newPathname}`;
|
||||||
}
|
}
|
||||||
const BASE_URL = getBaseUrl();
|
const BASE_URL = getBaseUrl();
|
||||||
|
|
||||||
|
4
src/static/scripts/admin_settings.js
vendored
4
src/static/scripts/admin_settings.js
vendored
@@ -19,7 +19,7 @@ function smtpTest(event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const data = JSON.stringify({ "email": test_email.value });
|
const data = JSON.stringify({ "email": test_email.value });
|
||||||
_post(`${BASE_URL}/admin/test/smtp/`,
|
_post(`${BASE_URL}/admin/test/smtp`,
|
||||||
"SMTP Test email sent correctly",
|
"SMTP Test email sent correctly",
|
||||||
"Error sending SMTP test email",
|
"Error sending SMTP test email",
|
||||||
data, false
|
data, false
|
||||||
@@ -45,7 +45,7 @@ function getFormData() {
|
|||||||
|
|
||||||
function saveConfig(event) {
|
function saveConfig(event) {
|
||||||
const data = JSON.stringify(getFormData());
|
const data = JSON.stringify(getFormData());
|
||||||
_post(`${BASE_URL}/admin/config/`,
|
_post(`${BASE_URL}/admin/config`,
|
||||||
"Config saved correctly",
|
"Config saved correctly",
|
||||||
"Error saving config",
|
"Error saving config",
|
||||||
data
|
data
|
||||||
|
2
src/static/scripts/admin_users.js
vendored
2
src/static/scripts/admin_users.js
vendored
@@ -113,7 +113,7 @@ function inviteUser(event) {
|
|||||||
"email": email.value
|
"email": email.value
|
||||||
});
|
});
|
||||||
email.value = "";
|
email.value = "";
|
||||||
_post(`${BASE_URL}/admin/invite/`,
|
_post(`${BASE_URL}/admin/invite`,
|
||||||
"User invited correctly",
|
"User invited correctly",
|
||||||
"Error inviting user",
|
"Error inviting user",
|
||||||
data
|
data
|
||||||
|
15
src/static/scripts/datatables.css
vendored
15
src/static/scripts/datatables.css
vendored
@@ -4,10 +4,10 @@
|
|||||||
*
|
*
|
||||||
* To rebuild or modify this file with the latest versions of the included
|
* To rebuild or modify this file with the latest versions of the included
|
||||||
* software please visit:
|
* software please visit:
|
||||||
* https://datatables.net/download/#bs5/dt-1.13.2
|
* https://datatables.net/download/#bs5/dt-1.13.4
|
||||||
*
|
*
|
||||||
* Included libraries:
|
* Included libraries:
|
||||||
* DataTables 1.13.2
|
* DataTables 1.13.4
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@charset "UTF-8";
|
@charset "UTF-8";
|
||||||
@@ -79,6 +79,7 @@ table.dataTable thead > tr > td.sorting_asc_disabled:before,
|
|||||||
table.dataTable thead > tr > td.sorting_desc_disabled:before {
|
table.dataTable thead > tr > td.sorting_desc_disabled:before {
|
||||||
bottom: 50%;
|
bottom: 50%;
|
||||||
content: "▲";
|
content: "▲";
|
||||||
|
content: "▲"/"";
|
||||||
}
|
}
|
||||||
table.dataTable thead > tr > th.sorting:after, table.dataTable thead > tr > th.sorting_asc:after, table.dataTable thead > tr > th.sorting_desc:after, table.dataTable thead > tr > th.sorting_asc_disabled:after, table.dataTable thead > tr > th.sorting_desc_disabled:after,
|
table.dataTable thead > tr > th.sorting:after, table.dataTable thead > tr > th.sorting_asc:after, table.dataTable thead > tr > th.sorting_desc:after, table.dataTable thead > tr > th.sorting_asc_disabled:after, table.dataTable thead > tr > th.sorting_desc_disabled:after,
|
||||||
table.dataTable thead > tr > td.sorting:after,
|
table.dataTable thead > tr > td.sorting:after,
|
||||||
@@ -88,6 +89,7 @@ table.dataTable thead > tr > td.sorting_asc_disabled:after,
|
|||||||
table.dataTable thead > tr > td.sorting_desc_disabled:after {
|
table.dataTable thead > tr > td.sorting_desc_disabled:after {
|
||||||
top: 50%;
|
top: 50%;
|
||||||
content: "▼";
|
content: "▼";
|
||||||
|
content: "▼"/"";
|
||||||
}
|
}
|
||||||
table.dataTable thead > tr > th.sorting_asc:before, table.dataTable thead > tr > th.sorting_desc:after,
|
table.dataTable thead > tr > th.sorting_asc:before, table.dataTable thead > tr > th.sorting_desc:after,
|
||||||
table.dataTable thead > tr > td.sorting_asc:before,
|
table.dataTable thead > tr > td.sorting_asc:before,
|
||||||
@@ -104,9 +106,9 @@ table.dataTable thead > tr > td:active {
|
|||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.dataTables_scrollBody table.dataTable thead > tr > th:before, div.dataTables_scrollBody table.dataTable thead > tr > th:after,
|
div.dataTables_scrollBody > table.dataTable > thead > tr > th:before, div.dataTables_scrollBody > table.dataTable > thead > tr > th:after,
|
||||||
div.dataTables_scrollBody table.dataTable thead > tr > td:before,
|
div.dataTables_scrollBody > table.dataTable > thead > tr > td:before,
|
||||||
div.dataTables_scrollBody table.dataTable thead > tr > td:after {
|
div.dataTables_scrollBody > table.dataTable > thead > tr > td:after {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,7 +134,8 @@ div.dataTables_processing > div:last-child > div {
|
|||||||
width: 13px;
|
width: 13px;
|
||||||
height: 13px;
|
height: 13px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background: 13 110 253;
|
background: rgb(13, 110, 253);
|
||||||
|
background: rgb(var(--dt-row-selected));
|
||||||
animation-timing-function: cubic-bezier(0, 1, 1, 0);
|
animation-timing-function: cubic-bezier(0, 1, 1, 0);
|
||||||
}
|
}
|
||||||
div.dataTables_processing > div:last-child > div:nth-child(1) {
|
div.dataTables_processing > div:last-child > div:nth-child(1) {
|
||||||
|
137
src/static/scripts/datatables.js
vendored
137
src/static/scripts/datatables.js
vendored
@@ -4,20 +4,20 @@
|
|||||||
*
|
*
|
||||||
* To rebuild or modify this file with the latest versions of the included
|
* To rebuild or modify this file with the latest versions of the included
|
||||||
* software please visit:
|
* software please visit:
|
||||||
* https://datatables.net/download/#bs5/dt-1.13.2
|
* https://datatables.net/download/#bs5/dt-1.13.4
|
||||||
*
|
*
|
||||||
* Included libraries:
|
* Included libraries:
|
||||||
* DataTables 1.13.2
|
* DataTables 1.13.4
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*! DataTables 1.13.2
|
/*! DataTables 1.13.4
|
||||||
* ©2008-2023 SpryMedia Ltd - datatables.net/license
|
* ©2008-2023 SpryMedia Ltd - datatables.net/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @summary DataTables
|
* @summary DataTables
|
||||||
* @description Paginate, search and order HTML tables
|
* @description Paginate, search and order HTML tables
|
||||||
* @version 1.13.2
|
* @version 1.13.4
|
||||||
* @author SpryMedia Ltd
|
* @author SpryMedia Ltd
|
||||||
* @contact www.datatables.net
|
* @contact www.datatables.net
|
||||||
* @copyright SpryMedia Ltd.
|
* @copyright SpryMedia Ltd.
|
||||||
@@ -46,21 +46,28 @@
|
|||||||
}
|
}
|
||||||
else if ( typeof exports === 'object' ) {
|
else if ( typeof exports === 'object' ) {
|
||||||
// CommonJS
|
// CommonJS
|
||||||
module.exports = function (root, $) {
|
// jQuery's factory checks for a global window - if it isn't present then it
|
||||||
if ( ! root ) {
|
// returns a factory function that expects the window object
|
||||||
// CommonJS environments without a window global must pass a
|
var jq = require('jquery');
|
||||||
// root. This will give an error otherwise
|
|
||||||
root = window;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( ! $ ) {
|
if (typeof window !== 'undefined') {
|
||||||
$ = typeof window !== 'undefined' ? // jQuery's factory checks for a global window
|
module.exports = function (root, $) {
|
||||||
require('jquery') :
|
if ( ! root ) {
|
||||||
require('jquery')( root );
|
// CommonJS environments without a window global must pass a
|
||||||
}
|
// root. This will give an error otherwise
|
||||||
|
root = window;
|
||||||
|
}
|
||||||
|
|
||||||
return factory( $, root, root.document );
|
if ( ! $ ) {
|
||||||
};
|
$ = jq( root );
|
||||||
|
}
|
||||||
|
|
||||||
|
return factory( $, root, root.document );
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return factory( jq, window, window.document );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Browser
|
// Browser
|
||||||
@@ -73,6 +80,12 @@
|
|||||||
|
|
||||||
var DataTable = function ( selector, options )
|
var DataTable = function ( selector, options )
|
||||||
{
|
{
|
||||||
|
// Check if called with a window or jQuery object for DOM less applications
|
||||||
|
// This is for backwards compatibility
|
||||||
|
if (DataTable.factory(selector, options)) {
|
||||||
|
return DataTable;
|
||||||
|
}
|
||||||
|
|
||||||
// When creating with `new`, create a new DataTable, returning the API instance
|
// When creating with `new`, create a new DataTable, returning the API instance
|
||||||
if (this instanceof DataTable) {
|
if (this instanceof DataTable) {
|
||||||
return $(selector).DataTable(options);
|
return $(selector).DataTable(options);
|
||||||
@@ -1177,6 +1190,7 @@
|
|||||||
type: sort !== null ? i+'.@data-'+sort : undefined,
|
type: sort !== null ? i+'.@data-'+sort : undefined,
|
||||||
filter: filter !== null ? i+'.@data-'+filter : undefined
|
filter: filter !== null ? i+'.@data-'+filter : undefined
|
||||||
};
|
};
|
||||||
|
col._isArrayHost = true;
|
||||||
|
|
||||||
_fnColumnOptions( oSettings, i );
|
_fnColumnOptions( oSettings, i );
|
||||||
}
|
}
|
||||||
@@ -2365,7 +2379,7 @@
|
|||||||
|
|
||||||
// Indicate if DataTables should read DOM data as an object or array
|
// Indicate if DataTables should read DOM data as an object or array
|
||||||
// Used in _fnGetRowElements
|
// Used in _fnGetRowElements
|
||||||
if ( typeof mDataSrc !== 'number' ) {
|
if ( typeof mDataSrc !== 'number' && ! oCol._isArrayHost ) {
|
||||||
oSettings._rowReadObject = true;
|
oSettings._rowReadObject = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5119,7 +5133,8 @@
|
|||||||
{
|
{
|
||||||
return $('<div/>', {
|
return $('<div/>', {
|
||||||
'id': ! settings.aanFeatures.r ? settings.sTableId+'_processing' : null,
|
'id': ! settings.aanFeatures.r ? settings.sTableId+'_processing' : null,
|
||||||
'class': settings.oClasses.sProcessing
|
'class': settings.oClasses.sProcessing,
|
||||||
|
'role': 'status'
|
||||||
} )
|
} )
|
||||||
.html( settings.oLanguage.sProcessing )
|
.html( settings.oLanguage.sProcessing )
|
||||||
.append('<div><div></div><div></div><div></div><div></div></div>')
|
.append('<div><div></div><div></div><div></div><div></div></div>')
|
||||||
@@ -9367,6 +9382,48 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the jQuery or window object to be used by DataTables
|
||||||
|
*
|
||||||
|
* @param {*} module Library / container object
|
||||||
|
* @param {string} type Library or container type `lib` or `win`.
|
||||||
|
*/
|
||||||
|
DataTable.use = function (module, type) {
|
||||||
|
if (type === 'lib' || module.fn) {
|
||||||
|
$ = module;
|
||||||
|
}
|
||||||
|
else if (type == 'win' || module.document) {
|
||||||
|
window = module;
|
||||||
|
document = module.document;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CommonJS factory function pass through. This will check if the arguments
|
||||||
|
* given are a window object or a jQuery object. If so they are set
|
||||||
|
* accordingly.
|
||||||
|
* @param {*} root Window
|
||||||
|
* @param {*} jq jQUery
|
||||||
|
* @returns {boolean} Indicator
|
||||||
|
*/
|
||||||
|
DataTable.factory = function (root, jq) {
|
||||||
|
var is = false;
|
||||||
|
|
||||||
|
// Test if the first parameter is a window object
|
||||||
|
if (root && root.document) {
|
||||||
|
window = root;
|
||||||
|
document = root.document;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test if the second parameter is a jQuery object
|
||||||
|
if (jq && jq.fn && jq.fn.jquery) {
|
||||||
|
$ = jq;
|
||||||
|
is = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return is;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provide a common method for plug-ins to check the version of DataTables being
|
* Provide a common method for plug-ins to check the version of DataTables being
|
||||||
* used, in order to ensure compatibility.
|
* used, in order to ensure compatibility.
|
||||||
@@ -9708,7 +9765,7 @@
|
|||||||
* @type string
|
* @type string
|
||||||
* @default Version number
|
* @default Version number
|
||||||
*/
|
*/
|
||||||
DataTable.version = "1.13.2";
|
DataTable.version = "1.13.4";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Private data store, containing all of the settings objects that are
|
* Private data store, containing all of the settings objects that are
|
||||||
@@ -14132,7 +14189,7 @@
|
|||||||
*
|
*
|
||||||
* @type string
|
* @type string
|
||||||
*/
|
*/
|
||||||
build:"bs5/dt-1.13.2",
|
build:"bs5/dt-1.13.4",
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -15654,25 +15711,33 @@
|
|||||||
}
|
}
|
||||||
else if ( typeof exports === 'object' ) {
|
else if ( typeof exports === 'object' ) {
|
||||||
// CommonJS
|
// CommonJS
|
||||||
module.exports = function (root, $) {
|
var jq = require('jquery');
|
||||||
if ( ! root ) {
|
var cjsRequires = function (root, $) {
|
||||||
// CommonJS environments without a window global must pass a
|
|
||||||
// root. This will give an error otherwise
|
|
||||||
root = window;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( ! $ ) {
|
|
||||||
$ = typeof window !== 'undefined' ? // jQuery's factory checks for a global window
|
|
||||||
require('jquery') :
|
|
||||||
require('jquery')( root );
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( ! $.fn.dataTable ) {
|
if ( ! $.fn.dataTable ) {
|
||||||
require('datatables.net')(root, $);
|
require('datatables.net')(root, $);
|
||||||
}
|
}
|
||||||
|
|
||||||
return factory( $, root, root.document );
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
module.exports = function (root, $) {
|
||||||
|
if ( ! root ) {
|
||||||
|
// CommonJS environments without a window global must pass a
|
||||||
|
// root. This will give an error otherwise
|
||||||
|
root = window;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! $ ) {
|
||||||
|
$ = jq( root );
|
||||||
|
}
|
||||||
|
|
||||||
|
cjsRequires( root, $ );
|
||||||
|
return factory( $, root, root.document );
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
cjsRequires( window, jq );
|
||||||
|
module.exports = factory( jq, window, window.document );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Browser
|
// Browser
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
/*!
|
/*!
|
||||||
* jQuery JavaScript Library v3.6.3 -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.4 -ajax,-ajax/jsonp,-ajax/load,-ajax/script,-ajax/var/location,-ajax/var/nonce,-ajax/var/rquery,-ajax/xhr,-manipulation/_evalUrl,-deprecated/ajax-event-alias,-effects,-effects/animatedSelector,-effects/Tween
|
||||||
* https://jquery.com/
|
* https://jquery.com/
|
||||||
*
|
*
|
||||||
* 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: 2022-12-20T21:28Z
|
* Date: 2023-03-08T15:29Z
|
||||||
*/
|
*/
|
||||||
( function( global, factory ) {
|
( function( global, factory ) {
|
||||||
|
|
||||||
@@ -151,7 +151,7 @@ function toType( obj ) {
|
|||||||
|
|
||||||
|
|
||||||
var
|
var
|
||||||
version = "3.6.3 -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.4 -ajax,-ajax/jsonp,-ajax/load,-ajax/script,-ajax/var/location,-ajax/var/nonce,-ajax/var/rquery,-ajax/xhr,-manipulation/_evalUrl,-deprecated/ajax-event-alias,-effects,-effects/animatedSelector,-effects/Tween",
|
||||||
|
|
||||||
// Define a local copy of jQuery
|
// Define a local copy of jQuery
|
||||||
jQuery = function( selector, context ) {
|
jQuery = function( selector, context ) {
|
||||||
@@ -522,14 +522,14 @@ function isArrayLike( obj ) {
|
|||||||
}
|
}
|
||||||
var Sizzle =
|
var Sizzle =
|
||||||
/*!
|
/*!
|
||||||
* Sizzle CSS Selector Engine v2.3.9
|
* Sizzle CSS Selector Engine v2.3.10
|
||||||
* https://sizzlejs.com/
|
* https://sizzlejs.com/
|
||||||
*
|
*
|
||||||
* Copyright JS Foundation and other contributors
|
* Copyright JS Foundation and other contributors
|
||||||
* Released under the MIT license
|
* Released under the MIT license
|
||||||
* https://js.foundation/
|
* https://js.foundation/
|
||||||
*
|
*
|
||||||
* Date: 2022-12-19
|
* Date: 2023-02-14
|
||||||
*/
|
*/
|
||||||
( function( window ) {
|
( function( window ) {
|
||||||
var i,
|
var i,
|
||||||
@@ -633,7 +633,7 @@ var i,
|
|||||||
whitespace + "+$", "g" ),
|
whitespace + "+$", "g" ),
|
||||||
|
|
||||||
rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ),
|
rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ),
|
||||||
rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace +
|
rleadingCombinator = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace +
|
||||||
"*" ),
|
"*" ),
|
||||||
rdescend = new RegExp( whitespace + "|>" ),
|
rdescend = new RegExp( whitespace + "|>" ),
|
||||||
|
|
||||||
@@ -850,7 +850,7 @@ function Sizzle( selector, context, results, seed ) {
|
|||||||
// as such selectors are not recognized by querySelectorAll.
|
// as such selectors are not recognized by querySelectorAll.
|
||||||
// Thanks to Andrew Dupont for this technique.
|
// Thanks to Andrew Dupont for this technique.
|
||||||
if ( nodeType === 1 &&
|
if ( nodeType === 1 &&
|
||||||
( rdescend.test( selector ) || rcombinators.test( selector ) ) ) {
|
( rdescend.test( selector ) || rleadingCombinator.test( selector ) ) ) {
|
||||||
|
|
||||||
// Expand context for sibling selectors
|
// Expand context for sibling selectors
|
||||||
newContext = rsibling.test( selector ) && testContext( context.parentNode ) ||
|
newContext = rsibling.test( selector ) && testContext( context.parentNode ) ||
|
||||||
@@ -879,27 +879,6 @@ function Sizzle( selector, context, results, seed ) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
// `qSA` may not throw for unrecognized parts using forgiving parsing:
|
|
||||||
// https://drafts.csswg.org/selectors/#forgiving-selector
|
|
||||||
// like the `:has()` pseudo-class:
|
|
||||||
// https://drafts.csswg.org/selectors/#relational
|
|
||||||
// `CSS.supports` is still expected to return `false` then:
|
|
||||||
// https://drafts.csswg.org/css-conditional-4/#typedef-supports-selector-fn
|
|
||||||
// https://drafts.csswg.org/css-conditional-4/#dfn-support-selector
|
|
||||||
if ( support.cssSupportsSelector &&
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-undef
|
|
||||||
!CSS.supports( "selector(:is(" + newSelector + "))" ) ) {
|
|
||||||
|
|
||||||
// Support: IE 11+
|
|
||||||
// Throw to get to the same code path as an error directly in qSA.
|
|
||||||
// Note: once we only support browser supporting
|
|
||||||
// `CSS.supports('selector(...)')`, we can most likely drop
|
|
||||||
// the `try-catch`. IE doesn't implement the API.
|
|
||||||
throw new Error();
|
|
||||||
}
|
|
||||||
|
|
||||||
push.apply( results,
|
push.apply( results,
|
||||||
newContext.querySelectorAll( newSelector )
|
newContext.querySelectorAll( newSelector )
|
||||||
);
|
);
|
||||||
@@ -1195,29 +1174,22 @@ setDocument = Sizzle.setDocument = function( node ) {
|
|||||||
!el.querySelectorAll( ":scope fieldset div" ).length;
|
!el.querySelectorAll( ":scope fieldset div" ).length;
|
||||||
} );
|
} );
|
||||||
|
|
||||||
// Support: Chrome 105+, Firefox 104+, Safari 15.4+
|
// Support: Chrome 105 - 110+, Safari 15.4 - 16.3+
|
||||||
// Make sure forgiving mode is not used in `CSS.supports( "selector(...)" )`.
|
// Make sure the the `:has()` argument is parsed unforgivingly.
|
||||||
//
|
// We include `*` in the test to detect buggy implementations that are
|
||||||
// `:is()` uses a forgiving selector list as an argument and is widely
|
// _selectively_ forgiving (specifically when the list includes at least
|
||||||
// implemented, so it's a good one to test against.
|
// one valid selector).
|
||||||
support.cssSupportsSelector = assert( function() {
|
// Note that we treat complete lack of support for `:has()` as if it were
|
||||||
/* eslint-disable no-undef */
|
// spec-compliant support, which is fine because use of `:has()` in such
|
||||||
|
// environments will fail in the qSA path and fall back to jQuery traversal
|
||||||
return CSS.supports( "selector(*)" ) &&
|
// anyway.
|
||||||
|
support.cssHas = assert( function() {
|
||||||
// Support: Firefox 78-81 only
|
try {
|
||||||
// In old Firefox, `:is()` didn't use forgiving parsing. In that case,
|
document.querySelector( ":has(*,:jqfake)" );
|
||||||
// fail this test as there's no selector to test against that.
|
return false;
|
||||||
// `CSS.supports` uses unforgiving parsing
|
} catch ( e ) {
|
||||||
document.querySelectorAll( ":is(:jqfake)" ) &&
|
return true;
|
||||||
|
}
|
||||||
// `*` is needed as Safari & newer Chrome implemented something in between
|
|
||||||
// for `:has()` - it throws in `qSA` if it only contains an unsupported
|
|
||||||
// argument but multiple ones, one of which is supported, are fine.
|
|
||||||
// We want to play safe in case `:is()` gets the same treatment.
|
|
||||||
!CSS.supports( "selector(:is(*,:jqfake))" );
|
|
||||||
|
|
||||||
/* eslint-enable */
|
|
||||||
} );
|
} );
|
||||||
|
|
||||||
/* Attributes
|
/* Attributes
|
||||||
@@ -1486,14 +1458,14 @@ setDocument = Sizzle.setDocument = function( node ) {
|
|||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( !support.cssSupportsSelector ) {
|
if ( !support.cssHas ) {
|
||||||
|
|
||||||
// Support: Chrome 105+, Safari 15.4+
|
// Support: Chrome 105 - 110+, Safari 15.4 - 16.3+
|
||||||
// `:has()` uses a forgiving selector list as an argument so our regular
|
// Our regular `try-catch` mechanism fails to detect natively-unsupported
|
||||||
// `try-catch` mechanism fails to catch `:has()` with arguments not supported
|
// pseudo-classes inside `:has()` (such as `:has(:contains("Foo"))`)
|
||||||
// natively like `:has(:contains("Foo"))`. Where supported & spec-compliant,
|
// in browsers that parse the `:has()` argument as a forgiving selector list.
|
||||||
// we now use `CSS.supports("selector(:is(SELECTOR_TO_BE_TESTED))")`, but
|
// https://drafts.csswg.org/selectors/#relational now requires the argument
|
||||||
// outside that we mark `:has` as buggy.
|
// to be parsed unforgivingly, but browsers have not yet fully adjusted.
|
||||||
rbuggyQSA.push( ":has" );
|
rbuggyQSA.push( ":has" );
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2406,7 +2378,7 @@ tokenize = Sizzle.tokenize = function( selector, parseOnly ) {
|
|||||||
matched = false;
|
matched = false;
|
||||||
|
|
||||||
// Combinators
|
// Combinators
|
||||||
if ( ( match = rcombinators.exec( soFar ) ) ) {
|
if ( ( match = rleadingCombinator.exec( soFar ) ) ) {
|
||||||
matched = match.shift();
|
matched = match.shift();
|
||||||
tokens.push( {
|
tokens.push( {
|
||||||
value: matched,
|
value: matched,
|
@@ -7,7 +7,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<th class="vw-org-details">Organization</th>
|
<th class="vw-org-details">Organization</th>
|
||||||
<th class="vw-users">Users</th>
|
<th class="vw-users">Users</th>
|
||||||
<th class="vw-ciphers">Ciphers</th>
|
<th class="vw-entries">Entries</th>
|
||||||
<th class="vw-attachments">Attachments</th>
|
<th class="vw-attachments">Attachments</th>
|
||||||
<th class="vw-misc">Misc</th>
|
<th class="vw-misc">Misc</th>
|
||||||
<th class="vw-actions">Actions</th>
|
<th class="vw-actions">Actions</th>
|
||||||
@@ -59,7 +59,7 @@
|
|||||||
</main>
|
</main>
|
||||||
|
|
||||||
<link rel="stylesheet" href="{{urlpath}}/vw_static/datatables.css" />
|
<link rel="stylesheet" href="{{urlpath}}/vw_static/datatables.css" />
|
||||||
<script src="{{urlpath}}/vw_static/jquery-3.6.3.slim.js"></script>
|
<script src="{{urlpath}}/vw_static/jquery-3.6.4.slim.js"></script>
|
||||||
<script src="{{urlpath}}/vw_static/datatables.js"></script>
|
<script src="{{urlpath}}/vw_static/datatables.js"></script>
|
||||||
<script src="{{urlpath}}/vw_static/admin_organizations.js"></script>
|
<script src="{{urlpath}}/vw_static/admin_organizations.js"></script>
|
||||||
<script src="{{urlpath}}/vw_static/jdenticon.js"></script>
|
<script src="{{urlpath}}/vw_static/jdenticon.js"></script>
|
||||||
|
@@ -8,7 +8,7 @@
|
|||||||
<th class="vw-account-details">User</th>
|
<th class="vw-account-details">User</th>
|
||||||
<th class="vw-created-at">Created at</th>
|
<th class="vw-created-at">Created at</th>
|
||||||
<th class="vw-last-active">Last Active</th>
|
<th class="vw-last-active">Last Active</th>
|
||||||
<th class="vw-ciphers">Ciphers</th>
|
<th class="vw-entries">Entries</th>
|
||||||
<th class="vw-attachments">Attachments</th>
|
<th class="vw-attachments">Attachments</th>
|
||||||
<th class="vw-organizations">Organizations</th>
|
<th class="vw-organizations">Organizations</th>
|
||||||
<th class="vw-actions">Actions</th>
|
<th class="vw-actions">Actions</th>
|
||||||
@@ -140,7 +140,7 @@
|
|||||||
</main>
|
</main>
|
||||||
|
|
||||||
<link rel="stylesheet" href="{{urlpath}}/vw_static/datatables.css" />
|
<link rel="stylesheet" href="{{urlpath}}/vw_static/datatables.css" />
|
||||||
<script src="{{urlpath}}/vw_static/jquery-3.6.3.slim.js"></script>
|
<script src="{{urlpath}}/vw_static/jquery-3.6.4.slim.js"></script>
|
||||||
<script src="{{urlpath}}/vw_static/datatables.js"></script>
|
<script src="{{urlpath}}/vw_static/datatables.js"></script>
|
||||||
<script src="{{urlpath}}/vw_static/admin_users.js"></script>
|
<script src="{{urlpath}}/vw_static/admin_users.js"></script>
|
||||||
<script src="{{urlpath}}/vw_static/jdenticon.js"></script>
|
<script src="{{urlpath}}/vw_static/jdenticon.js"></script>
|
||||||
|
@@ -1,6 +1,4 @@
|
|||||||
Master Password Has Been Changed
|
Master Password Has Been Changed
|
||||||
<!---------------->
|
<!---------------->
|
||||||
The master password for {{user_name}} has been changed by an administrator in your {{org_name}} organization. If you did not initiate this request, please reach out to your administrator immediately.
|
The master password for {{user_name}} has been changed by an administrator in your {{org_name}} organization. If you did not initiate this request, please reach out to your administrator immediately.
|
||||||
|
{{> email/email_footer_text }}
|
||||||
===
|
|
||||||
Github: https://github.com/dani-garcia/vaultwarden
|
|
||||||
|
@@ -3,6 +3,4 @@ Your Email Change
|
|||||||
To finalize changing your email address enter the following code in web vault: {{token}}
|
To finalize changing your email address enter the following code in web vault: {{token}}
|
||||||
|
|
||||||
If you did not try to change an email address, you can safely ignore this email.
|
If you did not try to change an email address, you can safely ignore this email.
|
||||||
|
{{> email/email_footer_text }}
|
||||||
===
|
|
||||||
Github: https://github.com/dani-garcia/vaultwarden
|
|
||||||
|
@@ -4,7 +4,7 @@ Delete Your Account
|
|||||||
<table width="100%" cellpadding="0" cellspacing="0" style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
|
<table width="100%" cellpadding="0" cellspacing="0" style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
|
||||||
<tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
|
<tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
|
||||||
<td class="content-block" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; margin: 0; -webkit-font-smoothing: antialiased; padding: 0 0 10px; -webkit-text-size-adjust: none; text-align: center;" valign="top" align="center">
|
<td class="content-block" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; margin: 0; -webkit-font-smoothing: antialiased; padding: 0 0 10px; -webkit-text-size-adjust: none; text-align: center;" valign="top" align="center">
|
||||||
click the link below to delete your account.
|
Click the link below to delete your account.
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
|
<tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
Invitation to {{{org_name}}} accepted
|
Invitation to {{{org_name}}} accepted
|
||||||
<!---------------->
|
<!---------------->
|
||||||
Your invitation for *{{email}}* to join *{{org_name}}* was accepted.
|
This email is to notify you that {{email}} has accepted your invitation to join {{org_name}}.
|
||||||
Please log in via {{url}} to the vaultwarden server and confirm them from the organization management page.
|
Please log in via {{url}} to the vaultwarden server and confirm them from the organization management page.
|
||||||
{{> email/email_footer_text }}
|
{{> email/email_footer_text }}
|
@@ -1,5 +1,5 @@
|
|||||||
Invitation to {{{org_name}}} confirmed
|
Invitation to {{{org_name}}} confirmed
|
||||||
<!---------------->
|
<!---------------->
|
||||||
Your invitation to join *{{org_name}}* was confirmed.
|
This email is to notify you that you have been confirmed as a user of {{org_name}}.
|
||||||
It will now appear under the Organizations the next time you log in to the web vault at {{url}}.
|
Any collections and logins being shared with you by this organization will now appear in your Vaultwarden vault at {{url}}.
|
||||||
{{> email/email_footer_text }}
|
{{> email/email_footer_text }}
|
@@ -4,6 +4,4 @@ You have been removed from organization *{{org_name}}* because your account does
|
|||||||
|
|
||||||
|
|
||||||
You can enable Two-step Login in your account settings.
|
You can enable Two-step Login in your account settings.
|
||||||
|
{{> email/email_footer_text }}
|
||||||
===
|
|
||||||
Github: https://github.com/dani-garcia/vaultwarden
|
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
You have been removed from {{{org_name}}}
|
You have been removed from {{{org_name}}}
|
||||||
<!---------------->
|
<!---------------->
|
||||||
Your user account has been removed from the *{{org_name}}* organization because you are a part of another organization. The {{org_name}} organization has enabled a policy that prevents users from being a part of multiple organizations. Before you can re-join this organization you need to leave all other organizations or join with a different account.
|
Your user account has been removed from the *{{org_name}}* organization because you are a part of another organization. The {{org_name}} organization has enabled a policy that prevents users from being a part of multiple organizations. Before you can re-join this organization you need to leave all other organizations or join with a different account.
|
||||||
===
|
{{> email/email_footer_text }}
|
||||||
Github: https://github.com/dani-garcia/vaultwarden
|
|
||||||
|
15
src/util.rs
15
src/util.rs
@@ -1,7 +1,10 @@
|
|||||||
//
|
//
|
||||||
// Web Headers and caching
|
// Web Headers and caching
|
||||||
//
|
//
|
||||||
use std::io::{Cursor, ErrorKind};
|
use std::{
|
||||||
|
io::{Cursor, ErrorKind},
|
||||||
|
ops::Deref,
|
||||||
|
};
|
||||||
|
|
||||||
use rocket::{
|
use rocket::{
|
||||||
fairing::{Fairing, Info, Kind},
|
fairing::{Fairing, Info, Kind},
|
||||||
@@ -209,6 +212,14 @@ impl std::fmt::Display for SafeString {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Deref for SafeString {
|
||||||
|
type Target = String;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl AsRef<Path> for SafeString {
|
impl AsRef<Path> for SafeString {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn as_ref(&self) -> &Path {
|
fn as_ref(&self) -> &Path {
|
||||||
@@ -231,7 +242,7 @@ impl<'r> FromParam<'r> for SafeString {
|
|||||||
|
|
||||||
// Log all the routes from the main paths list, and the attachments endpoint
|
// Log all the routes from the main paths list, and the attachments endpoint
|
||||||
// Effectively ignores, any static file route, and the alive endpoint
|
// Effectively ignores, any static file route, and the alive endpoint
|
||||||
const LOGGED_ROUTES: [&str; 5] = ["/api", "/admin", "/identity", "/icons", "/attachments"];
|
const LOGGED_ROUTES: [&str; 7] = ["/api", "/admin", "/identity", "/icons", "/attachments", "/events", "/notifications"];
|
||||||
|
|
||||||
// Boolean is extra debug, when true, we ignore the whitelist above and also print the mounts
|
// Boolean is extra debug, when true, we ignore the whitelist above and also print the mounts
|
||||||
pub struct BetterLogging(pub bool);
|
pub struct BetterLogging(pub bool);
|
||||||
|
Reference in New Issue
Block a user