mirror of
https://github.com/dani-garcia/vaultwarden.git
synced 2026-06-08 19:50:17 +03:00
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 62748100f0 | |||
| fcbdebd6d7 | |||
| 454b8e2a35 | |||
| 7883da554e | |||
| fd2b6528a9 | |||
| cc57e60886 | |||
| e5681258f0 | |||
| 7cf0c5d67e | |||
| b04ed75f9f | |||
| 0ed8ab68f7 | |||
| dfebee57ec | |||
| bfe420a018 | |||
| e7e4b9a86d | |||
| bb549986e6 |
@@ -1,3 +1,2 @@
|
|||||||
# Ignore vendored scripts in GitHub stats
|
# Ignore vendored scripts in GitHub stats
|
||||||
src/static/scripts/* linguist-vendored
|
src/static/scripts/* linguist-vendored
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ jobs:
|
|||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Run Trivy vulnerability scanner
|
- name: Run Trivy vulnerability scanner
|
||||||
uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # v0.35.0
|
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
|
||||||
env:
|
env:
|
||||||
TRIVY_DB_REPOSITORY: docker.io/aquasec/trivy-db:2,public.ecr.aws/aquasecurity/trivy-db:2,ghcr.io/aquasecurity/trivy-db:2
|
TRIVY_DB_REPOSITORY: docker.io/aquasec/trivy-db:2,public.ecr.aws/aquasecurity/trivy-db:2,ghcr.io/aquasecurity/trivy-db:2
|
||||||
TRIVY_JAVA_DB_REPOSITORY: docker.io/aquasec/trivy-java-db:1,public.ecr.aws/aquasecurity/trivy-java-db:1,ghcr.io/aquasecurity/trivy-java-db:1
|
TRIVY_JAVA_DB_REPOSITORY: docker.io/aquasec/trivy-java-db:1,public.ecr.aws/aquasecurity/trivy-java-db:1,ghcr.io/aquasecurity/trivy-java-db:1
|
||||||
@@ -50,6 +50,6 @@ jobs:
|
|||||||
severity: CRITICAL,HIGH
|
severity: CRITICAL,HIGH
|
||||||
|
|
||||||
- name: Upload Trivy scan results to GitHub Security tab
|
- name: Upload Trivy scan results to GitHub Security tab
|
||||||
uses: github/codeql-action/upload-sarif@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1
|
uses: github/codeql-action/upload-sarif@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2
|
||||||
with:
|
with:
|
||||||
sarif_file: 'trivy-results.sarif'
|
sarif_file: 'trivy-results.sarif'
|
||||||
|
|||||||
@@ -23,4 +23,4 @@ jobs:
|
|||||||
|
|
||||||
# When this version is updated, do not forget to update this in `.pre-commit-config.yaml` too
|
# When this version is updated, do not forget to update this in `.pre-commit-config.yaml` too
|
||||||
- name: Spell Check Repo
|
- name: Spell Check Repo
|
||||||
uses: crate-ci/typos@02ea592e44b3a53c302f697cddca7641cd051c3d # v1.45.0
|
uses: crate-ci/typos@cf5f1c29a8ac336af8568821ec41919923b05a83 # v1.45.1
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ jobs:
|
|||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Run zizmor
|
- name: Run zizmor
|
||||||
uses: zizmorcore/zizmor-action@71321a20a9ded102f6e9ce5718a2fcec2c4f70d8 # v0.5.2
|
uses: zizmorcore/zizmor-action@b1d7e1fb5de872772f31590499237e7cce841e8e # v0.5.3
|
||||||
with:
|
with:
|
||||||
# intentionally not scanning the entire repository,
|
# intentionally not scanning the entire repository,
|
||||||
# since it contains integration tests.
|
# since it contains integration tests.
|
||||||
|
|||||||
+55
-53
@@ -1,58 +1,60 @@
|
|||||||
---
|
---
|
||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
rev: 3e8a8703264a2f4a69428a0aa4dcb512790b2c8c # v6.0.0
|
rev: 3e8a8703264a2f4a69428a0aa4dcb512790b2c8c # v6.0.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: check-yaml
|
- id: check-yaml
|
||||||
- id: check-json
|
- id: check-json
|
||||||
- id: check-toml
|
- id: check-toml
|
||||||
- id: mixed-line-ending
|
- id: mixed-line-ending
|
||||||
args: ["--fix=no"]
|
args: [ "--fix=no" ]
|
||||||
- id: end-of-file-fixer
|
- id: end-of-file-fixer
|
||||||
exclude: "(.*js$|.*css$)"
|
exclude: "(.*js$|.*css$)"
|
||||||
- id: check-case-conflict
|
- id: check-case-conflict
|
||||||
- id: check-merge-conflict
|
- id: check-merge-conflict
|
||||||
- id: detect-private-key
|
- id: detect-private-key
|
||||||
- id: check-symlinks
|
- id: check-symlinks
|
||||||
- id: forbid-submodules
|
- id: forbid-submodules
|
||||||
- repo: local
|
|
||||||
|
# When this version is updated, do not forget to update this in `.github/workflows/typos.yaml` too
|
||||||
|
- repo: https://github.com/crate-ci/typos
|
||||||
|
rev: cf5f1c29a8ac336af8568821ec41919923b05a83 # v1.45.1
|
||||||
hooks:
|
hooks:
|
||||||
- id: fmt
|
- id: typos
|
||||||
name: fmt
|
|
||||||
description: Format files with cargo fmt.
|
- repo: local
|
||||||
entry: cargo fmt
|
hooks:
|
||||||
language: system
|
- id: fmt
|
||||||
always_run: true
|
name: fmt
|
||||||
pass_filenames: false
|
description: Format files with cargo fmt.
|
||||||
args: ["--", "--check"]
|
entry: cargo fmt
|
||||||
- id: cargo-test
|
language: system
|
||||||
name: cargo test
|
always_run: true
|
||||||
description: Test the package for errors.
|
pass_filenames: false
|
||||||
entry: cargo test
|
args: [ "--", "--check" ]
|
||||||
language: system
|
- id: cargo-test
|
||||||
args: ["--features", "sqlite,mysql,postgresql", "--"]
|
name: cargo test
|
||||||
types_or: [rust, file]
|
description: Test the package for errors.
|
||||||
files: (Cargo.toml|Cargo.lock|rust-toolchain.toml|rustfmt.toml|.*\.rs$)
|
entry: cargo test
|
||||||
pass_filenames: false
|
language: system
|
||||||
- id: cargo-clippy
|
args: [ "--features", "sqlite,mysql,postgresql", "--" ]
|
||||||
name: cargo clippy
|
types_or: [ rust, file ]
|
||||||
description: Lint Rust sources
|
files: (Cargo.toml|Cargo.lock|rust-toolchain.toml|rustfmt.toml|.*\.rs$)
|
||||||
entry: cargo clippy
|
pass_filenames: false
|
||||||
language: system
|
- id: cargo-clippy
|
||||||
args: ["--features", "sqlite,mysql,postgresql", "--", "-D", "warnings"]
|
name: cargo clippy
|
||||||
types_or: [rust, file]
|
description: Lint Rust sources
|
||||||
files: (Cargo.toml|Cargo.lock|rust-toolchain.toml|rustfmt.toml|.*\.rs$)
|
entry: cargo clippy
|
||||||
pass_filenames: false
|
language: system
|
||||||
- id: check-docker-templates
|
args: [ "--features", "sqlite,mysql,postgresql", "--", "-D", "warnings" ]
|
||||||
name: check docker templates
|
types_or: [ rust, file ]
|
||||||
description: Check if the Docker templates are updated
|
files: (Cargo.toml|Cargo.lock|rust-toolchain.toml|rustfmt.toml|.*\.rs$)
|
||||||
language: system
|
pass_filenames: false
|
||||||
entry: sh
|
- id: check-docker-templates
|
||||||
args:
|
name: check docker templates
|
||||||
- "-c"
|
description: Check if the Docker templates are updated
|
||||||
- "cd docker && make"
|
language: system
|
||||||
# When this version is updated, do not forget to update this in `.github/workflows/typos.yaml` too
|
entry: sh
|
||||||
- repo: https://github.com/crate-ci/typos
|
args:
|
||||||
rev: 02ea592e44b3a53c302f697cddca7641cd051c3d # v1.45.0
|
- "-c"
|
||||||
hooks:
|
- "cd docker && make"
|
||||||
- id: typos
|
|
||||||
|
|||||||
Generated
+356
-153
File diff suppressed because it is too large
Load Diff
+10
-8
@@ -1,6 +1,6 @@
|
|||||||
[workspace.package]
|
[workspace.package]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
rust-version = "1.92.0"
|
rust-version = "1.93.0"
|
||||||
license = "AGPL-3.0-only"
|
license = "AGPL-3.0-only"
|
||||||
repository = "https://github.com/dani-garcia/vaultwarden"
|
repository = "https://github.com/dani-garcia/vaultwarden"
|
||||||
publish = false
|
publish = false
|
||||||
@@ -79,7 +79,7 @@ dashmap = "6.1.0"
|
|||||||
|
|
||||||
# Async futures
|
# Async futures
|
||||||
futures = "0.3.32"
|
futures = "0.3.32"
|
||||||
tokio = { version = "1.51.1", features = ["rt-multi-thread", "fs", "io-util", "parking_lot", "time", "signal", "net"] }
|
tokio = { version = "1.52.1", features = ["rt-multi-thread", "fs", "io-util", "parking_lot", "time", "signal", "net"] }
|
||||||
tokio-util = { version = "0.7.18", features = ["compat"]}
|
tokio-util = { version = "0.7.18", features = ["compat"]}
|
||||||
|
|
||||||
# A generic serialization/deserialization framework
|
# A generic serialization/deserialization framework
|
||||||
@@ -103,7 +103,7 @@ ring = "0.17.14"
|
|||||||
subtle = "2.6.1"
|
subtle = "2.6.1"
|
||||||
|
|
||||||
# UUID generation
|
# UUID generation
|
||||||
uuid = { version = "1.23.0", features = ["v4"] }
|
uuid = { version = "1.23.1", features = ["v4"] }
|
||||||
|
|
||||||
# Date and time libraries
|
# Date and time libraries
|
||||||
chrono = { version = "0.4.44", features = ["clock", "serde"], default-features = false }
|
chrono = { version = "0.4.44", features = ["clock", "serde"], default-features = false }
|
||||||
@@ -145,7 +145,7 @@ handlebars = { version = "6.4.0", features = ["dir_source"] }
|
|||||||
|
|
||||||
# HTTP client (Used for favicons, version check, DUO and HIBP API)
|
# HTTP client (Used for favicons, version check, DUO and HIBP API)
|
||||||
reqwest = { version = "0.12.28", features = ["rustls-tls", "rustls-tls-native-roots", "stream", "json", "deflate", "gzip", "brotli", "zstd", "socks", "cookies", "charset", "http2", "system-proxy"], default-features = false}
|
reqwest = { version = "0.12.28", features = ["rustls-tls", "rustls-tls-native-roots", "stream", "json", "deflate", "gzip", "brotli", "zstd", "socks", "cookies", "charset", "http2", "system-proxy"], default-features = false}
|
||||||
hickory-resolver = "0.25.2"
|
hickory-resolver = "0.26.0"
|
||||||
|
|
||||||
# Favicon extraction libraries
|
# Favicon extraction libraries
|
||||||
html5gum = "0.8.3"
|
html5gum = "0.8.3"
|
||||||
@@ -162,7 +162,7 @@ cookie = "0.18.1"
|
|||||||
cookie_store = "0.22.1"
|
cookie_store = "0.22.1"
|
||||||
|
|
||||||
# Used by U2F, JWT and PostgreSQL
|
# Used by U2F, JWT and PostgreSQL
|
||||||
openssl = "0.10.76"
|
openssl = "0.10.78"
|
||||||
|
|
||||||
# CLI argument parsing
|
# CLI argument parsing
|
||||||
pico-args = "0.5.0"
|
pico-args = "0.5.0"
|
||||||
@@ -180,7 +180,7 @@ semver = "1.0.28"
|
|||||||
|
|
||||||
# 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.48", features = ["secure"], default-features = false, optional = true }
|
mimalloc = { version = "0.1.50", features = ["secure"], default-features = false, optional = true }
|
||||||
|
|
||||||
which = "8.0.2"
|
which = "8.0.2"
|
||||||
|
|
||||||
@@ -198,9 +198,9 @@ opendal = { version = "0.55.0", features = ["services-fs"], default-features = f
|
|||||||
|
|
||||||
# For retrieving AWS credentials, including temporary SSO credentials
|
# For retrieving AWS credentials, including temporary SSO credentials
|
||||||
anyhow = { version = "1.0.102", optional = true }
|
anyhow = { version = "1.0.102", optional = true }
|
||||||
aws-config = { version = "1.8.15", features = ["behavior-version-latest", "rt-tokio", "credentials-process", "sso"], default-features = false, optional = true }
|
aws-config = { version = "1.8.16", features = ["behavior-version-latest", "rt-tokio", "credentials-process", "sso"], default-features = false, optional = true }
|
||||||
aws-credential-types = { version = "1.2.14", optional = true }
|
aws-credential-types = { version = "1.2.14", optional = true }
|
||||||
aws-smithy-runtime-api = { version = "1.11.6", optional = true }
|
aws-smithy-runtime-api = { version = "1.12.0", optional = true }
|
||||||
http = { version = "1.4.0", optional = true }
|
http = { version = "1.4.0", optional = true }
|
||||||
reqsign = { version = "0.16.5", optional = true }
|
reqsign = { version = "0.16.5", optional = true }
|
||||||
|
|
||||||
@@ -301,6 +301,7 @@ branches_sharing_code = "deny"
|
|||||||
case_sensitive_file_extension_comparisons = "deny"
|
case_sensitive_file_extension_comparisons = "deny"
|
||||||
cast_lossless = "deny"
|
cast_lossless = "deny"
|
||||||
clone_on_ref_ptr = "deny"
|
clone_on_ref_ptr = "deny"
|
||||||
|
duration_suboptimal_units = "deny"
|
||||||
equatable_if_let = "deny"
|
equatable_if_let = "deny"
|
||||||
excessive_precision = "deny"
|
excessive_precision = "deny"
|
||||||
filter_map_next = "deny"
|
filter_map_next = "deny"
|
||||||
@@ -322,6 +323,7 @@ needless_continue = "deny"
|
|||||||
needless_lifetimes = "deny"
|
needless_lifetimes = "deny"
|
||||||
option_option = "deny"
|
option_option = "deny"
|
||||||
redundant_clone = "deny"
|
redundant_clone = "deny"
|
||||||
|
ref_option = "deny"
|
||||||
string_add_assign = "deny"
|
string_add_assign = "deny"
|
||||||
unnecessary_join = "deny"
|
unnecessary_join = "deny"
|
||||||
unnecessary_self_imports = "deny"
|
unnecessary_self_imports = "deny"
|
||||||
|
|||||||
+1
-1
@@ -2,4 +2,4 @@
|
|||||||
# see diesel.rs/guides/configuring-diesel-cli
|
# see diesel.rs/guides/configuring-diesel-cli
|
||||||
|
|
||||||
[print_schema]
|
[print_schema]
|
||||||
file = "src/db/schema.rs"
|
file = "src/db/schema.rs"
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
---
|
---
|
||||||
vault_version: "v2026.2.0"
|
vault_version: "v2026.3.1"
|
||||||
vault_image_digest: "sha256:37c8661fa59dcdfbd3baa8366b6e950ef292b15adfeff1f57812b075c1fd3447"
|
vault_image_digest: "sha256:c1b1f212333f95bff4ef8d00e8e3589c4ae8eda018691f28f8bddc7e971dd767"
|
||||||
# Cross Compile Docker Helper Scripts v1.9.0
|
# Cross Compile Docker Helper Scripts v1.9.0
|
||||||
# We use the linux/amd64 platform shell scripts since there is no difference between the different platform scripts
|
# We use the linux/amd64 platform shell scripts since there is no difference between the different platform scripts
|
||||||
# https://github.com/tonistiigi/xx | https://hub.docker.com/r/tonistiigi/xx/tags
|
# https://github.com/tonistiigi/xx | https://hub.docker.com/r/tonistiigi/xx/tags
|
||||||
xx_image_digest: "sha256:c64defb9ed5a91eacb37f96ccc3d4cd72521c4bd18d5442905b95e2226b0e707"
|
xx_image_digest: "sha256:c64defb9ed5a91eacb37f96ccc3d4cd72521c4bd18d5442905b95e2226b0e707"
|
||||||
rust_version: 1.94.1 # Rust version to be used
|
rust_version: 1.95.0 # Rust version to be used
|
||||||
debian_version: trixie # Debian release name to be used
|
debian_version: trixie # Debian release name to be used
|
||||||
alpine_version: "3.23" # Alpine version to be used
|
alpine_version: "3.23" # Alpine version to be used
|
||||||
# For which platforms/architectures will we try to build images
|
# For which platforms/architectures will we try to build images
|
||||||
|
|||||||
+10
-10
@@ -19,23 +19,23 @@
|
|||||||
# - 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 docker.io/vaultwarden/web-vault:v2026.2.0
|
# $ docker pull docker.io/vaultwarden/web-vault:v2026.3.1
|
||||||
# $ docker image inspect --format "{{.RepoDigests}}" docker.io/vaultwarden/web-vault:v2026.2.0
|
# $ docker image inspect --format "{{.RepoDigests}}" docker.io/vaultwarden/web-vault:v2026.3.1
|
||||||
# [docker.io/vaultwarden/web-vault@sha256:37c8661fa59dcdfbd3baa8366b6e950ef292b15adfeff1f57812b075c1fd3447]
|
# [docker.io/vaultwarden/web-vault@sha256:c1b1f212333f95bff4ef8d00e8e3589c4ae8eda018691f28f8bddc7e971dd767]
|
||||||
#
|
#
|
||||||
# - Conversely, to get the tag name from the digest:
|
# - Conversely, to get the tag name from the digest:
|
||||||
# $ docker image inspect --format "{{.RepoTags}}" docker.io/vaultwarden/web-vault@sha256:37c8661fa59dcdfbd3baa8366b6e950ef292b15adfeff1f57812b075c1fd3447
|
# $ docker image inspect --format "{{.RepoTags}}" docker.io/vaultwarden/web-vault@sha256:c1b1f212333f95bff4ef8d00e8e3589c4ae8eda018691f28f8bddc7e971dd767
|
||||||
# [docker.io/vaultwarden/web-vault:v2026.2.0]
|
# [docker.io/vaultwarden/web-vault:v2026.3.1]
|
||||||
#
|
#
|
||||||
FROM --platform=linux/amd64 docker.io/vaultwarden/web-vault@sha256:37c8661fa59dcdfbd3baa8366b6e950ef292b15adfeff1f57812b075c1fd3447 AS vault
|
FROM --platform=linux/amd64 docker.io/vaultwarden/web-vault@sha256:c1b1f212333f95bff4ef8d00e8e3589c4ae8eda018691f28f8bddc7e971dd767 AS vault
|
||||||
|
|
||||||
########################## ALPINE BUILD IMAGES ##########################
|
########################## ALPINE BUILD IMAGES ##########################
|
||||||
## NOTE: The Alpine Base Images do not support other platforms then linux/amd64 and linux/arm64
|
## NOTE: The Alpine Base Images do not support other platforms then linux/amd64 and linux/arm64
|
||||||
## And for Alpine we define all build images here, they will only be loaded when actually used
|
## And for Alpine we define all build images here, they will only be loaded when actually used
|
||||||
FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:x86_64-musl-stable-1.94.1 AS build_amd64
|
FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:x86_64-musl-stable-1.95.0 AS build_amd64
|
||||||
FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:aarch64-musl-stable-1.94.1 AS build_arm64
|
FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:aarch64-musl-stable-1.95.0 AS build_arm64
|
||||||
FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:armv7-musleabihf-stable-1.94.1 AS build_armv7
|
FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:armv7-musleabihf-stable-1.95.0 AS build_armv7
|
||||||
FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:arm-musleabi-stable-1.94.1 AS build_armv6
|
FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:arm-musleabi-stable-1.95.0 AS build_armv6
|
||||||
|
|
||||||
########################## BUILD IMAGE ##########################
|
########################## BUILD IMAGE ##########################
|
||||||
# hadolint ignore=DL3006
|
# hadolint ignore=DL3006
|
||||||
|
|||||||
@@ -19,15 +19,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 docker.io/vaultwarden/web-vault:v2026.2.0
|
# $ docker pull docker.io/vaultwarden/web-vault:v2026.3.1
|
||||||
# $ docker image inspect --format "{{.RepoDigests}}" docker.io/vaultwarden/web-vault:v2026.2.0
|
# $ docker image inspect --format "{{.RepoDigests}}" docker.io/vaultwarden/web-vault:v2026.3.1
|
||||||
# [docker.io/vaultwarden/web-vault@sha256:37c8661fa59dcdfbd3baa8366b6e950ef292b15adfeff1f57812b075c1fd3447]
|
# [docker.io/vaultwarden/web-vault@sha256:c1b1f212333f95bff4ef8d00e8e3589c4ae8eda018691f28f8bddc7e971dd767]
|
||||||
#
|
#
|
||||||
# - Conversely, to get the tag name from the digest:
|
# - Conversely, to get the tag name from the digest:
|
||||||
# $ docker image inspect --format "{{.RepoTags}}" docker.io/vaultwarden/web-vault@sha256:37c8661fa59dcdfbd3baa8366b6e950ef292b15adfeff1f57812b075c1fd3447
|
# $ docker image inspect --format "{{.RepoTags}}" docker.io/vaultwarden/web-vault@sha256:c1b1f212333f95bff4ef8d00e8e3589c4ae8eda018691f28f8bddc7e971dd767
|
||||||
# [docker.io/vaultwarden/web-vault:v2026.2.0]
|
# [docker.io/vaultwarden/web-vault:v2026.3.1]
|
||||||
#
|
#
|
||||||
FROM --platform=linux/amd64 docker.io/vaultwarden/web-vault@sha256:37c8661fa59dcdfbd3baa8366b6e950ef292b15adfeff1f57812b075c1fd3447 AS vault
|
FROM --platform=linux/amd64 docker.io/vaultwarden/web-vault@sha256:c1b1f212333f95bff4ef8d00e8e3589c4ae8eda018691f28f8bddc7e971dd767 AS vault
|
||||||
|
|
||||||
########################## Cross Compile Docker Helper Scripts ##########################
|
########################## Cross Compile Docker Helper Scripts ##########################
|
||||||
## We use the linux/amd64 no matter which Build Platform, since these are all bash scripts
|
## We use the linux/amd64 no matter which Build Platform, since these are all bash scripts
|
||||||
@@ -36,7 +36,7 @@ FROM --platform=linux/amd64 docker.io/tonistiigi/xx@sha256:c64defb9ed5a91eacb37f
|
|||||||
|
|
||||||
########################## BUILD IMAGE ##########################
|
########################## BUILD IMAGE ##########################
|
||||||
# hadolint ignore=DL3006
|
# hadolint ignore=DL3006
|
||||||
FROM --platform=$BUILDPLATFORM docker.io/library/rust:1.94.1-slim-trixie AS build
|
FROM --platform=$BUILDPLATFORM docker.io/library/rust:1.95.0-slim-trixie AS build
|
||||||
COPY --from=xx / /
|
COPY --from=xx / /
|
||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
ARG TARGETVARIANT
|
ARG TARGETVARIANT
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
[toolchain]
|
[toolchain]
|
||||||
channel = "1.94.1"
|
channel = "1.95.0"
|
||||||
components = [ "rustfmt", "clippy" ]
|
components = [ "rustfmt", "clippy" ]
|
||||||
profile = "minimal"
|
profile = "minimal"
|
||||||
|
|||||||
+12
-3
@@ -30,6 +30,7 @@ use crate::{
|
|||||||
error::{Error, MapResult},
|
error::{Error, MapResult},
|
||||||
http_client::make_http_request,
|
http_client::make_http_request,
|
||||||
mail,
|
mail,
|
||||||
|
sso::FAKE_SSO_IDENTIFIER,
|
||||||
util::{
|
util::{
|
||||||
container_base_image, format_naive_datetime_local, get_active_web_release, get_display_size,
|
container_base_image, format_naive_datetime_local, get_active_web_release, get_display_size,
|
||||||
is_running_in_container, parse_experimental_client_feature_flags, FeatureFlagFilter, NumberOrString,
|
is_running_in_container, parse_experimental_client_feature_flags, FeatureFlagFilter, NumberOrString,
|
||||||
@@ -315,7 +316,11 @@ async fn invite_user(data: Json<InviteData>, _token: AdminToken, conn: DbConn) -
|
|||||||
|
|
||||||
async fn _generate_invite(user: &User, conn: &DbConn) -> EmptyResult {
|
async fn _generate_invite(user: &User, conn: &DbConn) -> EmptyResult {
|
||||||
if CONFIG.mail_enabled() {
|
if CONFIG.mail_enabled() {
|
||||||
let org_id: OrganizationId = FAKE_ADMIN_UUID.to_string().into();
|
let org_id: OrganizationId = if CONFIG.sso_enabled() {
|
||||||
|
FAKE_SSO_IDENTIFIER.into()
|
||||||
|
} else {
|
||||||
|
FAKE_ADMIN_UUID.into()
|
||||||
|
};
|
||||||
let member_id: MembershipId = FAKE_ADMIN_UUID.to_string().into();
|
let member_id: MembershipId = FAKE_ADMIN_UUID.to_string().into();
|
||||||
mail::send_invite(user, org_id, member_id, &CONFIG.invitation_org_name(), None).await
|
mail::send_invite(user, org_id, member_id, &CONFIG.invitation_org_name(), None).await
|
||||||
} else {
|
} else {
|
||||||
@@ -464,7 +469,7 @@ async fn deauth_user(user_id: UserId, _token: AdminToken, conn: DbConn, nt: Noti
|
|||||||
|
|
||||||
if CONFIG.push_enabled() {
|
if CONFIG.push_enabled() {
|
||||||
for device in Device::find_push_devices_by_user(&user.uuid, &conn).await {
|
for device in Device::find_push_devices_by_user(&user.uuid, &conn).await {
|
||||||
match unregister_push_device(&device.push_uuid).await {
|
match unregister_push_device(device.push_uuid.as_ref()).await {
|
||||||
Ok(r) => r,
|
Ok(r) => r,
|
||||||
Err(e) => error!("Unable to unregister devices from Bitwarden server: {e}"),
|
Err(e) => error!("Unable to unregister devices from Bitwarden server: {e}"),
|
||||||
};
|
};
|
||||||
@@ -518,7 +523,11 @@ async fn resend_user_invite(user_id: UserId, _token: AdminToken, conn: DbConn) -
|
|||||||
}
|
}
|
||||||
|
|
||||||
if CONFIG.mail_enabled() {
|
if CONFIG.mail_enabled() {
|
||||||
let org_id: OrganizationId = FAKE_ADMIN_UUID.to_string().into();
|
let org_id: OrganizationId = if CONFIG.sso_enabled() {
|
||||||
|
FAKE_SSO_IDENTIFIER.into()
|
||||||
|
} else {
|
||||||
|
FAKE_ADMIN_UUID.into()
|
||||||
|
};
|
||||||
let member_id: MembershipId = FAKE_ADMIN_UUID.to_string().into();
|
let member_id: MembershipId = FAKE_ADMIN_UUID.to_string().into();
|
||||||
mail::send_invite(&user, org_id, member_id, &CONFIG.invitation_org_name(), None).await
|
mail::send_invite(&user, org_id, member_id, &CONFIG.invitation_org_name(), None).await
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+10
-10
@@ -137,7 +137,7 @@ struct KeysData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Trims whitespace from password hints, and converts blank password hints to `None`.
|
/// Trims whitespace from password hints, and converts blank password hints to `None`.
|
||||||
fn clean_password_hint(password_hint: &Option<String>) -> Option<String> {
|
fn clean_password_hint(password_hint: Option<&String>) -> Option<String> {
|
||||||
match password_hint {
|
match password_hint {
|
||||||
None => None,
|
None => None,
|
||||||
Some(h) => match h.trim() {
|
Some(h) => match h.trim() {
|
||||||
@@ -147,7 +147,7 @@ fn clean_password_hint(password_hint: &Option<String>) -> Option<String> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn enforce_password_hint_setting(password_hint: &Option<String>) -> EmptyResult {
|
fn enforce_password_hint_setting(password_hint: Option<&String>) -> EmptyResult {
|
||||||
if password_hint.is_some() && !CONFIG.password_hints_allowed() {
|
if password_hint.is_some() && !CONFIG.password_hints_allowed() {
|
||||||
err!("Password hints have been disabled by the administrator. Remove the hint and try again.");
|
err!("Password hints have been disabled by the administrator. Remove the hint and try again.");
|
||||||
}
|
}
|
||||||
@@ -245,8 +245,8 @@ pub async fn _register(data: Json<RegisterData>, email_verification: bool, conn:
|
|||||||
|
|
||||||
// Check against the password hint setting here so if it fails, the user
|
// Check against the password hint setting here so if it fails, the user
|
||||||
// can retry without losing their invitation below.
|
// can retry without losing their invitation below.
|
||||||
let password_hint = clean_password_hint(&data.master_password_hint);
|
let password_hint = clean_password_hint(data.master_password_hint.as_ref());
|
||||||
enforce_password_hint_setting(&password_hint)?;
|
enforce_password_hint_setting(password_hint.as_ref())?;
|
||||||
|
|
||||||
let mut user = match User::find_by_mail(&email, &conn).await {
|
let mut user = match User::find_by_mail(&email, &conn).await {
|
||||||
Some(user) => {
|
Some(user) => {
|
||||||
@@ -353,8 +353,8 @@ async fn post_set_password(data: Json<SetPasswordData>, headers: Headers, conn:
|
|||||||
|
|
||||||
// Check against the password hint setting here so if it fails,
|
// Check against the password hint setting here so if it fails,
|
||||||
// the user can retry without losing their invitation below.
|
// the user can retry without losing their invitation below.
|
||||||
let password_hint = clean_password_hint(&data.master_password_hint);
|
let password_hint = clean_password_hint(data.master_password_hint.as_ref());
|
||||||
enforce_password_hint_setting(&password_hint)?;
|
enforce_password_hint_setting(password_hint.as_ref())?;
|
||||||
|
|
||||||
set_kdf_data(&mut user, &data.kdf)?;
|
set_kdf_data(&mut user, &data.kdf)?;
|
||||||
|
|
||||||
@@ -374,7 +374,7 @@ async fn post_set_password(data: Json<SetPasswordData>, headers: Headers, conn:
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(identifier) = data.org_identifier {
|
if let Some(identifier) = data.org_identifier {
|
||||||
if identifier != crate::sso::FAKE_IDENTIFIER && identifier != crate::api::admin::FAKE_ADMIN_UUID {
|
if identifier != crate::sso::FAKE_SSO_IDENTIFIER && identifier != crate::api::admin::FAKE_ADMIN_UUID {
|
||||||
let Some(org) = Organization::find_by_uuid(&identifier.into(), &conn).await else {
|
let Some(org) = Organization::find_by_uuid(&identifier.into(), &conn).await else {
|
||||||
err!("Failed to retrieve the associated organization")
|
err!("Failed to retrieve the associated organization")
|
||||||
};
|
};
|
||||||
@@ -515,8 +515,8 @@ async fn post_password(data: Json<ChangePassData>, headers: Headers, conn: DbCon
|
|||||||
err!("Invalid password")
|
err!("Invalid password")
|
||||||
}
|
}
|
||||||
|
|
||||||
user.password_hint = clean_password_hint(&data.master_password_hint);
|
user.password_hint = clean_password_hint(data.master_password_hint.as_ref());
|
||||||
enforce_password_hint_setting(&user.password_hint)?;
|
enforce_password_hint_setting(user.password_hint.as_ref())?;
|
||||||
|
|
||||||
log_user_event(EventType::UserChangedPassword as i32, &user.uuid, headers.device.atype, &headers.ip.ip, &conn)
|
log_user_event(EventType::UserChangedPassword as i32, &user.uuid, headers.device.atype, &headers.ip.ip, &conn)
|
||||||
.await;
|
.await;
|
||||||
@@ -1438,7 +1438,7 @@ async fn put_clear_device_token(device_id: DeviceId, conn: DbConn) -> EmptyResul
|
|||||||
|
|
||||||
if let Some(device) = Device::find_by_uuid(&device_id, &conn).await {
|
if let Some(device) = Device::find_by_uuid(&device_id, &conn).await {
|
||||||
Device::clear_push_token_by_uuid(&device_id, &conn).await?;
|
Device::clear_push_token_by_uuid(&device_id, &conn).await?;
|
||||||
unregister_push_device(&device.push_uuid).await?;
|
unregister_push_device(device.push_uuid.as_ref()).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -630,7 +630,7 @@ async fn post_ciphers_import(data: Json<ImportData>, headers: Headers, conn: DbC
|
|||||||
|
|
||||||
let mut user = headers.user;
|
let mut user = headers.user;
|
||||||
user.update_revision(&conn).await?;
|
user.update_revision(&conn).await?;
|
||||||
nt.send_user_update(UpdateType::SyncVault, &user, &headers.device.push_uuid, &conn).await;
|
nt.send_user_update(UpdateType::SyncVault, &user, headers.device.push_uuid.as_ref(), &conn).await;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -1005,7 +1005,7 @@ async fn put_cipher_share_selected(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Multi share actions do not send out a push for each cipher, we need to send a general sync here
|
// Multi share actions do not send out a push for each cipher, we need to send a general sync here
|
||||||
nt.send_user_update(UpdateType::SyncCiphers, &headers.user, &headers.device.push_uuid, &conn).await;
|
nt.send_user_update(UpdateType::SyncCiphers, &headers.user, headers.device.push_uuid.as_ref(), &conn).await;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -1618,7 +1618,7 @@ async fn move_cipher_selected(
|
|||||||
.await;
|
.await;
|
||||||
} else {
|
} else {
|
||||||
// Multi move actions do not send out a push for each cipher, we need to send a general sync here
|
// Multi move actions do not send out a push for each cipher, we need to send a general sync here
|
||||||
nt.send_user_update(UpdateType::SyncCiphers, &headers.user, &headers.device.push_uuid, &conn).await;
|
nt.send_user_update(UpdateType::SyncCiphers, &headers.user, headers.device.push_uuid.as_ref(), &conn).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
if cipher_count != accessible_ciphers_count {
|
if cipher_count != accessible_ciphers_count {
|
||||||
@@ -1670,7 +1670,7 @@ async fn purge_org_vault(
|
|||||||
match Membership::find_confirmed_by_user_and_org(&user.uuid, &organization.org_id, &conn).await {
|
match Membership::find_confirmed_by_user_and_org(&user.uuid, &organization.org_id, &conn).await {
|
||||||
Some(member) if member.atype == MembershipType::Owner => {
|
Some(member) if member.atype == MembershipType::Owner => {
|
||||||
Cipher::delete_all_by_organization(&organization.org_id, &conn).await?;
|
Cipher::delete_all_by_organization(&organization.org_id, &conn).await?;
|
||||||
nt.send_user_update(UpdateType::SyncVault, &user, &headers.device.push_uuid, &conn).await;
|
nt.send_user_update(UpdateType::SyncVault, &user, headers.device.push_uuid.as_ref(), &conn).await;
|
||||||
|
|
||||||
log_event(
|
log_event(
|
||||||
EventType::OrganizationPurgedVault as i32,
|
EventType::OrganizationPurgedVault as i32,
|
||||||
@@ -1710,7 +1710,7 @@ async fn purge_personal_vault(
|
|||||||
}
|
}
|
||||||
|
|
||||||
user.update_revision(&conn).await?;
|
user.update_revision(&conn).await?;
|
||||||
nt.send_user_update(UpdateType::SyncVault, &user, &headers.device.push_uuid, &conn).await;
|
nt.send_user_update(UpdateType::SyncVault, &user, headers.device.push_uuid.as_ref(), &conn).await;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -1805,7 +1805,7 @@ async fn _delete_multiple_ciphers(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Multi delete actions do not send out a push for each cipher, we need to send a general sync here
|
// Multi delete actions do not send out a push for each cipher, we need to send a general sync here
|
||||||
nt.send_user_update(UpdateType::SyncCiphers, &headers.user, &headers.device.push_uuid, &conn).await;
|
nt.send_user_update(UpdateType::SyncCiphers, &headers.user, headers.device.push_uuid.as_ref(), &conn).await;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -1873,7 +1873,7 @@ async fn _restore_multiple_ciphers(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Multi move actions do not send out a push for each cipher, we need to send a general sync here
|
// Multi move actions do not send out a push for each cipher, we need to send a general sync here
|
||||||
nt.send_user_update(UpdateType::SyncCiphers, &headers.user, &headers.device.push_uuid, conn).await;
|
nt.send_user_update(UpdateType::SyncCiphers, &headers.user, headers.device.push_uuid.as_ref(), conn).await;
|
||||||
|
|
||||||
Ok(Json(json!({
|
Ok(Json(json!({
|
||||||
"data": ciphers,
|
"data": ciphers,
|
||||||
|
|||||||
+1
-1
@@ -124,7 +124,7 @@ async fn post_eq_domains(data: Json<EquivDomainData>, headers: Headers, conn: Db
|
|||||||
|
|
||||||
user.save(&conn).await?;
|
user.save(&conn).await?;
|
||||||
|
|
||||||
nt.send_user_update(UpdateType::SyncSettings, &user, &headers.device.push_uuid, &conn).await;
|
nt.send_user_update(UpdateType::SyncSettings, &user, headers.device.push_uuid.as_ref(), &conn).await;
|
||||||
|
|
||||||
Ok(Json(json!({})))
|
Ok(Json(json!({})))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,8 @@ use crate::{
|
|||||||
DbConn,
|
DbConn,
|
||||||
},
|
},
|
||||||
mail,
|
mail,
|
||||||
util::{convert_json_key_lcase_first, get_uuid, NumberOrString},
|
sso::FAKE_SSO_IDENTIFIER,
|
||||||
|
util::{convert_json_key_lcase_first, NumberOrString},
|
||||||
CONFIG,
|
CONFIG,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -64,6 +65,7 @@ pub fn routes() -> Vec<Route> {
|
|||||||
post_org_import,
|
post_org_import,
|
||||||
list_policies,
|
list_policies,
|
||||||
list_policies_token,
|
list_policies_token,
|
||||||
|
get_dummy_master_password_policy,
|
||||||
get_master_password_policy,
|
get_master_password_policy,
|
||||||
get_policy,
|
get_policy,
|
||||||
put_policy,
|
put_policy,
|
||||||
@@ -99,6 +101,7 @@ pub fn routes() -> Vec<Route> {
|
|||||||
get_billing_metadata,
|
get_billing_metadata,
|
||||||
get_billing_warnings,
|
get_billing_warnings,
|
||||||
get_auto_enroll_status,
|
get_auto_enroll_status,
|
||||||
|
get_self_host_billing_metadata,
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -353,7 +356,7 @@ async fn get_user_collections(headers: Headers, conn: DbConn) -> Json<Value> {
|
|||||||
// The returned `Id` will then be passed to `get_master_password_policy` which will mainly ignore it
|
// The returned `Id` will then be passed to `get_master_password_policy` which will mainly ignore it
|
||||||
#[get("/organizations/<identifier>/auto-enroll-status")]
|
#[get("/organizations/<identifier>/auto-enroll-status")]
|
||||||
async fn get_auto_enroll_status(identifier: &str, headers: Headers, conn: DbConn) -> JsonResult {
|
async fn get_auto_enroll_status(identifier: &str, headers: Headers, conn: DbConn) -> JsonResult {
|
||||||
let org = if identifier == crate::sso::FAKE_IDENTIFIER {
|
let org = if identifier == FAKE_SSO_IDENTIFIER {
|
||||||
match Membership::find_main_user_org(&headers.user.uuid, &conn).await {
|
match Membership::find_main_user_org(&headers.user.uuid, &conn).await {
|
||||||
Some(member) => Organization::find_by_uuid(&member.org_uuid, &conn).await,
|
Some(member) => Organization::find_by_uuid(&member.org_uuid, &conn).await,
|
||||||
None => None,
|
None => None,
|
||||||
@@ -363,7 +366,7 @@ async fn get_auto_enroll_status(identifier: &str, headers: Headers, conn: DbConn
|
|||||||
};
|
};
|
||||||
|
|
||||||
let (id, identifier, rp_auto_enroll) = match org {
|
let (id, identifier, rp_auto_enroll) = match org {
|
||||||
None => (get_uuid(), identifier.to_string(), false),
|
None => (identifier.to_string(), identifier.to_string(), false),
|
||||||
Some(org) => (
|
Some(org) => (
|
||||||
org.uuid.to_string(),
|
org.uuid.to_string(),
|
||||||
org.uuid.to_string(),
|
org.uuid.to_string(),
|
||||||
@@ -924,7 +927,7 @@ async fn get_org_domain_sso_verified(data: Json<OrgDomainDetails>, conn: DbConn)
|
|||||||
.collect::<Vec<(String, String)>>()
|
.collect::<Vec<(String, String)>>()
|
||||||
{
|
{
|
||||||
v if !v.is_empty() => v,
|
v if !v.is_empty() => v,
|
||||||
_ => vec![(crate::sso::FAKE_IDENTIFIER.to_string(), crate::sso::FAKE_IDENTIFIER.to_string())],
|
_ => vec![(FAKE_SSO_IDENTIFIER.to_string(), FAKE_SSO_IDENTIFIER.to_string())],
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Json(json!({
|
Ok(Json(json!({
|
||||||
@@ -1460,7 +1463,7 @@ async fn _confirm_invite(
|
|||||||
let save_result = member_to_confirm.save(conn).await;
|
let save_result = member_to_confirm.save(conn).await;
|
||||||
|
|
||||||
if let Some(user) = User::find_by_uuid(&member_to_confirm.user_uuid, conn).await {
|
if let Some(user) = User::find_by_uuid(&member_to_confirm.user_uuid, conn).await {
|
||||||
nt.send_user_update(UpdateType::SyncOrgKeys, &user, &headers.device.push_uuid, conn).await;
|
nt.send_user_update(UpdateType::SyncOrgKeys, &user, headers.device.push_uuid.as_ref(), conn).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
save_result
|
save_result
|
||||||
@@ -1718,7 +1721,7 @@ async fn _delete_member(
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
if let Some(user) = User::find_by_uuid(&member_to_delete.user_uuid, conn).await {
|
if let Some(user) = User::find_by_uuid(&member_to_delete.user_uuid, conn).await {
|
||||||
nt.send_user_update(UpdateType::SyncOrgKeys, &user, &headers.device.push_uuid, conn).await;
|
nt.send_user_update(UpdateType::SyncOrgKeys, &user, headers.device.push_uuid.as_ref(), conn).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
member_to_delete.delete(conn).await
|
member_to_delete.delete(conn).await
|
||||||
@@ -1975,9 +1978,19 @@ async fn list_policies_token(org_id: OrganizationId, token: &str, conn: DbConn)
|
|||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Called during the SSO enrollment.
|
// Called during the SSO enrollment return the default policy
|
||||||
// Return the org policy if it exists, otherwise use the default one.
|
#[get("/organizations/00000000-01DC-01DC-01DC-000000000000/policies/master-password", rank = 1)]
|
||||||
#[get("/organizations/<org_id>/policies/master-password", rank = 1)]
|
fn get_dummy_master_password_policy() -> JsonResult {
|
||||||
|
let (enabled, data) = match CONFIG.sso_master_password_policy_value() {
|
||||||
|
Some(policy) if CONFIG.sso_enabled() => (true, policy.to_string()),
|
||||||
|
_ => (false, "null".to_string()),
|
||||||
|
};
|
||||||
|
let policy = OrgPolicy::new(FAKE_SSO_IDENTIFIER.into(), OrgPolicyType::MasterPassword, enabled, data);
|
||||||
|
Ok(Json(policy.to_json()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called during the SSO enrollment return the org policy if it exists
|
||||||
|
#[get("/organizations/<org_id>/policies/master-password", rank = 2)]
|
||||||
async fn get_master_password_policy(org_id: OrganizationId, _headers: OrgMemberHeaders, conn: DbConn) -> JsonResult {
|
async fn get_master_password_policy(org_id: OrganizationId, _headers: OrgMemberHeaders, conn: DbConn) -> JsonResult {
|
||||||
let policy =
|
let policy =
|
||||||
OrgPolicy::find_by_org_and_type(&org_id, OrgPolicyType::MasterPassword, &conn).await.unwrap_or_else(|| {
|
OrgPolicy::find_by_org_and_type(&org_id, OrgPolicyType::MasterPassword, &conn).await.unwrap_or_else(|| {
|
||||||
@@ -1992,7 +2005,7 @@ async fn get_master_password_policy(org_id: OrganizationId, _headers: OrgMemberH
|
|||||||
Ok(Json(policy.to_json()))
|
Ok(Json(policy.to_json()))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/organizations/<org_id>/policies/<pol_type>", rank = 2)]
|
#[get("/organizations/<org_id>/policies/<pol_type>", rank = 3)]
|
||||||
async fn get_policy(org_id: OrganizationId, pol_type: i32, headers: AdminHeaders, conn: DbConn) -> JsonResult {
|
async fn get_policy(org_id: OrganizationId, pol_type: i32, headers: AdminHeaders, conn: DbConn) -> JsonResult {
|
||||||
if org_id != headers.org_id {
|
if org_id != headers.org_id {
|
||||||
err!("Organization not found", "Organization id's do not match");
|
err!("Organization not found", "Organization id's do not match");
|
||||||
@@ -2201,6 +2214,15 @@ fn get_billing_warnings(_org_id: OrganizationId, _headers: OrgMemberHeaders) ->
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[get("/organizations/<_org_id>/billing/vnext/self-host/metadata")]
|
||||||
|
fn get_self_host_billing_metadata(_org_id: OrganizationId, _headers: OrgMemberHeaders) -> Json<Value> {
|
||||||
|
// Prevent a 404 error, which also causes Javascript errors.
|
||||||
|
Json(json!({
|
||||||
|
"isOnSecretsManagerStandalone": false, // Secrets Manager is not supported by Vaultwarden
|
||||||
|
"organizationOccupiedSeats": 0 // Vaultwarden does not count seats
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
fn _empty_data_json() -> Value {
|
fn _empty_data_json() -> Value {
|
||||||
json!({
|
json!({
|
||||||
"object": "list",
|
"object": "list",
|
||||||
|
|||||||
@@ -574,7 +574,7 @@ async fn download_url(host: &Host, send_id: &SendId, file_id: &SendFileId) -> Re
|
|||||||
|
|
||||||
Ok(format!("{}/api/sends/{send_id}/{file_id}?t={token}", &host.host))
|
Ok(format!("{}/api/sends/{send_id}/{file_id}?t={token}", &host.host))
|
||||||
} else {
|
} else {
|
||||||
Ok(operator.presign_read(&format!("{send_id}/{file_id}"), Duration::from_secs(5 * 60)).await?.uri().to_string())
|
Ok(operator.presign_read(&format!("{send_id}/{file_id}"), Duration::from_mins(5)).await?.uri().to_string())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ static WEBAUTHN: LazyLock<Webauthn> = LazyLock::new(|| {
|
|||||||
let webauthn = WebauthnBuilder::new(&rp_id, &rp_origin)
|
let webauthn = WebauthnBuilder::new(&rp_id, &rp_origin)
|
||||||
.expect("Creating WebauthnBuilder failed")
|
.expect("Creating WebauthnBuilder failed")
|
||||||
.rp_name(&domain)
|
.rp_name(&domain)
|
||||||
.timeout(Duration::from_millis(60000));
|
.timeout(Duration::from_mins(1));
|
||||||
|
|
||||||
webauthn.build().expect("Building Webauthn failed")
|
webauthn.build().expect("Building Webauthn failed")
|
||||||
});
|
});
|
||||||
|
|||||||
+49
-36
@@ -2,7 +2,6 @@ use chrono::Utc;
|
|||||||
use num_traits::FromPrimitive;
|
use num_traits::FromPrimitive;
|
||||||
use rocket::{
|
use rocket::{
|
||||||
form::{Form, FromForm},
|
form::{Form, FromForm},
|
||||||
http::Status,
|
|
||||||
response::Redirect,
|
response::Redirect,
|
||||||
serde::json::Json,
|
serde::json::Json,
|
||||||
Route,
|
Route,
|
||||||
@@ -12,7 +11,7 @@ use serde_json::Value;
|
|||||||
use crate::{
|
use crate::{
|
||||||
api::{
|
api::{
|
||||||
core::{
|
core::{
|
||||||
accounts::{PreloginData, RegisterData, _prelogin, _register, kdf_upgrade},
|
accounts::{_prelogin, _register, kdf_upgrade, PreloginData, RegisterData},
|
||||||
log_user_event,
|
log_user_event,
|
||||||
two_factor::{
|
two_factor::{
|
||||||
authenticator, duo, duo_oidc, email, enforce_2fa_policy, is_twofactor_provider_usable, webauthn,
|
authenticator, duo, duo_oidc, email, enforce_2fa_policy, is_twofactor_provider_usable, webauthn,
|
||||||
@@ -42,6 +41,7 @@ pub fn routes() -> Vec<Route> {
|
|||||||
routes![
|
routes![
|
||||||
login,
|
login,
|
||||||
prelogin,
|
prelogin,
|
||||||
|
prelogin_password,
|
||||||
identity_register,
|
identity_register,
|
||||||
register_verification_email,
|
register_verification_email,
|
||||||
register_finish,
|
register_finish,
|
||||||
@@ -65,43 +65,43 @@ async fn login(
|
|||||||
|
|
||||||
let login_result = match data.grant_type.as_ref() {
|
let login_result = match data.grant_type.as_ref() {
|
||||||
"refresh_token" => {
|
"refresh_token" => {
|
||||||
_check_is_some(&data.refresh_token, "refresh_token cannot be blank")?;
|
_check_is_some(data.refresh_token.as_ref(), "refresh_token cannot be blank")?;
|
||||||
_refresh_login(data, &conn, &client_header.ip).await
|
_refresh_login(data, &conn, &client_header.ip).await
|
||||||
}
|
}
|
||||||
"password" if CONFIG.sso_enabled() && CONFIG.sso_only() => err!("SSO sign-in is required"),
|
"password" if CONFIG.sso_enabled() && CONFIG.sso_only() => err!("SSO sign-in is required"),
|
||||||
"password" => {
|
"password" => {
|
||||||
_check_is_some(&data.client_id, "client_id cannot be blank")?;
|
_check_is_some(data.client_id.as_ref(), "client_id cannot be blank")?;
|
||||||
_check_is_some(&data.password, "password cannot be blank")?;
|
_check_is_some(data.password.as_ref(), "password cannot be blank")?;
|
||||||
_check_is_some(&data.scope, "scope cannot be blank")?;
|
_check_is_some(data.scope.as_ref(), "scope cannot be blank")?;
|
||||||
_check_is_some(&data.username, "username cannot be blank")?;
|
_check_is_some(data.username.as_ref(), "username cannot be blank")?;
|
||||||
|
|
||||||
_check_is_some(&data.device_identifier, "device_identifier cannot be blank")?;
|
_check_is_some(data.device_identifier.as_ref(), "device_identifier cannot be blank")?;
|
||||||
_check_is_some(&data.device_name, "device_name cannot be blank")?;
|
_check_is_some(data.device_name.as_ref(), "device_name cannot be blank")?;
|
||||||
_check_is_some(&data.device_type, "device_type cannot be blank")?;
|
_check_is_some(data.device_type.as_ref(), "device_type cannot be blank")?;
|
||||||
|
|
||||||
_password_login(data, &mut user_id, &conn, &client_header.ip, &client_version).await
|
_password_login(data, &mut user_id, &conn, &client_header.ip, client_version.as_ref()).await
|
||||||
}
|
}
|
||||||
"client_credentials" => {
|
"client_credentials" => {
|
||||||
_check_is_some(&data.client_id, "client_id cannot be blank")?;
|
_check_is_some(data.client_id.as_ref(), "client_id cannot be blank")?;
|
||||||
_check_is_some(&data.client_secret, "client_secret cannot be blank")?;
|
_check_is_some(data.client_secret.as_ref(), "client_secret cannot be blank")?;
|
||||||
_check_is_some(&data.scope, "scope cannot be blank")?;
|
_check_is_some(data.scope.as_ref(), "scope cannot be blank")?;
|
||||||
|
|
||||||
_check_is_some(&data.device_identifier, "device_identifier cannot be blank")?;
|
_check_is_some(data.device_identifier.as_ref(), "device_identifier cannot be blank")?;
|
||||||
_check_is_some(&data.device_name, "device_name cannot be blank")?;
|
_check_is_some(data.device_name.as_ref(), "device_name cannot be blank")?;
|
||||||
_check_is_some(&data.device_type, "device_type cannot be blank")?;
|
_check_is_some(data.device_type.as_ref(), "device_type cannot be blank")?;
|
||||||
|
|
||||||
_api_key_login(data, &mut user_id, &conn, &client_header.ip).await
|
_api_key_login(data, &mut user_id, &conn, &client_header.ip).await
|
||||||
}
|
}
|
||||||
"authorization_code" if CONFIG.sso_enabled() => {
|
"authorization_code" if CONFIG.sso_enabled() => {
|
||||||
_check_is_some(&data.client_id, "client_id cannot be blank")?;
|
_check_is_some(data.client_id.as_ref(), "client_id cannot be blank")?;
|
||||||
_check_is_some(&data.code, "code cannot be blank")?;
|
_check_is_some(data.code.as_ref(), "code cannot be blank")?;
|
||||||
_check_is_some(&data.code_verifier, "code verifier cannot be blank")?;
|
_check_is_some(data.code_verifier.as_ref(), "code verifier cannot be blank")?;
|
||||||
|
|
||||||
_check_is_some(&data.device_identifier, "device_identifier cannot be blank")?;
|
_check_is_some(data.device_identifier.as_ref(), "device_identifier cannot be blank")?;
|
||||||
_check_is_some(&data.device_name, "device_name cannot be blank")?;
|
_check_is_some(data.device_name.as_ref(), "device_name cannot be blank")?;
|
||||||
_check_is_some(&data.device_type, "device_type cannot be blank")?;
|
_check_is_some(data.device_type.as_ref(), "device_type cannot be blank")?;
|
||||||
|
|
||||||
_sso_login(data, &mut user_id, &conn, &client_header.ip, &client_version).await
|
_sso_login(data, &mut user_id, &conn, &client_header.ip, client_version.as_ref()).await
|
||||||
}
|
}
|
||||||
"authorization_code" => err!("SSO sign-in is not available"),
|
"authorization_code" => err!("SSO sign-in is not available"),
|
||||||
t => err!("Invalid type", t),
|
t => err!("Invalid type", t),
|
||||||
@@ -131,12 +131,14 @@ async fn login(
|
|||||||
login_result
|
login_result
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return Status::Unauthorized to trigger logout
|
|
||||||
async fn _refresh_login(data: ConnectData, conn: &DbConn, ip: &ClientIp) -> JsonResult {
|
async fn _refresh_login(data: ConnectData, conn: &DbConn, ip: &ClientIp) -> JsonResult {
|
||||||
// Extract token
|
// When a refresh token is invalid or missing we need to respond with an HTTP BadRequest (400)
|
||||||
let refresh_token = match data.refresh_token {
|
// It also needs to return a json which holds at least a key `error` with the value `invalid_grant`
|
||||||
Some(token) => token,
|
// See the link below for details
|
||||||
None => err_code!("Missing refresh_token", Status::Unauthorized.code),
|
// https://github.com/bitwarden/clients/blob/2ee158e720a5e7dbe3641caf80b569e97a1dd91b/libs/common/src/services/api.service.ts#L1786-L1797
|
||||||
|
|
||||||
|
let Some(refresh_token) = data.refresh_token else {
|
||||||
|
err_json!(json!({"error": "invalid_grant"}), "Missing refresh_token")
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---
|
// ---
|
||||||
@@ -147,7 +149,10 @@ async fn _refresh_login(data: ConnectData, conn: &DbConn, ip: &ClientIp) -> Json
|
|||||||
// let members = Membership::find_confirmed_by_user(&user.uuid, conn).await;
|
// let members = Membership::find_confirmed_by_user(&user.uuid, conn).await;
|
||||||
match auth::refresh_tokens(ip, &refresh_token, data.client_id, conn).await {
|
match auth::refresh_tokens(ip, &refresh_token, data.client_id, conn).await {
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
err_code!(format!("Unable to refresh login credentials: {}", err.message()), Status::Unauthorized.code)
|
err_json!(
|
||||||
|
json!({"error": "invalid_grant"}),
|
||||||
|
format!("Unable to refresh login credentials: {}", err.message())
|
||||||
|
)
|
||||||
}
|
}
|
||||||
Ok((mut device, auth_tokens)) => {
|
Ok((mut device, auth_tokens)) => {
|
||||||
// Save to update `device.updated_at` to track usage and toggle new status
|
// Save to update `device.updated_at` to track usage and toggle new status
|
||||||
@@ -172,7 +177,7 @@ async fn _sso_login(
|
|||||||
user_id: &mut Option<UserId>,
|
user_id: &mut Option<UserId>,
|
||||||
conn: &DbConn,
|
conn: &DbConn,
|
||||||
ip: &ClientIp,
|
ip: &ClientIp,
|
||||||
client_version: &Option<ClientVersion>,
|
client_version: Option<&ClientVersion>,
|
||||||
) -> JsonResult {
|
) -> JsonResult {
|
||||||
AuthMethod::Sso.check_scope(data.scope.as_ref())?;
|
AuthMethod::Sso.check_scope(data.scope.as_ref())?;
|
||||||
|
|
||||||
@@ -315,7 +320,7 @@ async fn _password_login(
|
|||||||
user_id: &mut Option<UserId>,
|
user_id: &mut Option<UserId>,
|
||||||
conn: &DbConn,
|
conn: &DbConn,
|
||||||
ip: &ClientIp,
|
ip: &ClientIp,
|
||||||
client_version: &Option<ClientVersion>,
|
client_version: Option<&ClientVersion>,
|
||||||
) -> JsonResult {
|
) -> JsonResult {
|
||||||
// Validate scope
|
// Validate scope
|
||||||
AuthMethod::Password.check_scope(data.scope.as_ref())?;
|
AuthMethod::Password.check_scope(data.scope.as_ref())?;
|
||||||
@@ -729,7 +734,7 @@ async fn twofactor_auth(
|
|||||||
data: &ConnectData,
|
data: &ConnectData,
|
||||||
device: &mut Device,
|
device: &mut Device,
|
||||||
ip: &ClientIp,
|
ip: &ClientIp,
|
||||||
client_version: &Option<ClientVersion>,
|
client_version: Option<&ClientVersion>,
|
||||||
conn: &DbConn,
|
conn: &DbConn,
|
||||||
) -> ApiResult<Option<String>> {
|
) -> ApiResult<Option<String>> {
|
||||||
let twofactors = TwoFactor::find_by_user(&user.uuid, conn).await;
|
let twofactors = TwoFactor::find_by_user(&user.uuid, conn).await;
|
||||||
@@ -754,7 +759,10 @@ async fn twofactor_auth(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let selected_id = data.two_factor_provider.unwrap_or(twofactor_ids[0]); // If we aren't given a two factor provider, assume the first one
|
let selected_id = data.two_factor_provider.unwrap_or(twofactor_ids[0]); // If we aren't given a two factor provider, assume the first one
|
||||||
if !twofactor_ids.contains(&selected_id) {
|
// Ignore Remember and RecoveryCode Types during this check, these are special
|
||||||
|
if ![TwoFactorType::Remember as i32, TwoFactorType::RecoveryCode as i32].contains(&selected_id)
|
||||||
|
&& !twofactor_ids.contains(&selected_id)
|
||||||
|
{
|
||||||
err_json!(
|
err_json!(
|
||||||
_json_err_twofactor(&twofactor_ids, &user.uuid, data, client_version, conn).await?,
|
_json_err_twofactor(&twofactor_ids, &user.uuid, data, client_version, conn).await?,
|
||||||
"Invalid two factor provider"
|
"Invalid two factor provider"
|
||||||
@@ -871,7 +879,7 @@ async fn _json_err_twofactor(
|
|||||||
providers: &[i32],
|
providers: &[i32],
|
||||||
user_id: &UserId,
|
user_id: &UserId,
|
||||||
data: &ConnectData,
|
data: &ConnectData,
|
||||||
client_version: &Option<ClientVersion>,
|
client_version: Option<&ClientVersion>,
|
||||||
conn: &DbConn,
|
conn: &DbConn,
|
||||||
) -> ApiResult<Value> {
|
) -> ApiResult<Value> {
|
||||||
let mut result = json!({
|
let mut result = json!({
|
||||||
@@ -975,6 +983,11 @@ async fn prelogin(data: Json<PreloginData>, conn: DbConn) -> Json<Value> {
|
|||||||
_prelogin(data, conn).await
|
_prelogin(data, conn).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[post("/accounts/prelogin/password", data = "<data>")]
|
||||||
|
async fn prelogin_password(data: Json<PreloginData>, conn: DbConn) -> Json<Value> {
|
||||||
|
_prelogin(data, conn).await
|
||||||
|
}
|
||||||
|
|
||||||
#[post("/accounts/register", data = "<data>")]
|
#[post("/accounts/register", data = "<data>")]
|
||||||
async fn identity_register(data: Json<RegisterData>, conn: DbConn) -> JsonResult {
|
async fn identity_register(data: Json<RegisterData>, conn: DbConn) -> JsonResult {
|
||||||
_register(data, false, conn).await
|
_register(data, false, conn).await
|
||||||
@@ -1101,7 +1114,7 @@ struct ConnectData {
|
|||||||
#[field(name = uncased("code_verifier"))]
|
#[field(name = uncased("code_verifier"))]
|
||||||
code_verifier: Option<OIDCCodeVerifier>,
|
code_verifier: Option<OIDCCodeVerifier>,
|
||||||
}
|
}
|
||||||
fn _check_is_some<T>(value: &Option<T>, msg: &str) -> EmptyResult {
|
fn _check_is_some<T>(value: Option<&T>, msg: &str) -> EmptyResult {
|
||||||
if value.is_none() {
|
if value.is_none() {
|
||||||
err!(msg)
|
err!(msg)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -338,7 +338,7 @@ impl WebSocketUsers {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: The last modified date needs to be updated before calling these methods
|
// NOTE: The last modified date needs to be updated before calling these methods
|
||||||
pub async fn send_user_update(&self, ut: UpdateType, user: &User, push_uuid: &Option<PushId>, conn: &DbConn) {
|
pub async fn send_user_update(&self, ut: UpdateType, user: &User, push_uuid: Option<&PushId>, conn: &DbConn) {
|
||||||
// Skip any processing if both WebSockets and Push are not active
|
// Skip any processing if both WebSockets and Push are not active
|
||||||
if *NOTIFICATIONS_DISABLED {
|
if *NOTIFICATIONS_DISABLED {
|
||||||
return;
|
return;
|
||||||
|
|||||||
+2
-2
@@ -135,7 +135,7 @@ pub async fn register_push_device(device: &mut Device, conn: &DbConn) -> EmptyRe
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn unregister_push_device(push_id: &Option<PushId>) -> EmptyResult {
|
pub async fn unregister_push_device(push_id: Option<&PushId>) -> EmptyResult {
|
||||||
if !CONFIG.push_enabled() || push_id.is_none() {
|
if !CONFIG.push_enabled() || push_id.is_none() {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
@@ -206,7 +206,7 @@ pub async fn push_logout(user: &User, acting_device: Option<&Device>, conn: &DbC
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn push_user_update(ut: UpdateType, user: &User, push_uuid: &Option<PushId>, conn: &DbConn) {
|
pub async fn push_user_update(ut: UpdateType, user: &User, push_uuid: Option<&PushId>, conn: &DbConn) {
|
||||||
if Device::check_user_has_push_device(&user.uuid, conn).await {
|
if Device::check_user_has_push_device(&user.uuid, conn).await {
|
||||||
tokio::task::spawn(send_to_push_relay(json!({
|
tokio::task::spawn(send_to_push_relay(json!({
|
||||||
"userId": user.uuid,
|
"userId": user.uuid,
|
||||||
|
|||||||
+3
-3
@@ -1076,7 +1076,7 @@ fn validate_config(cfg: &ConfigItems, on_update: bool) -> Result<(), Error> {
|
|||||||
|
|
||||||
validate_internal_sso_issuer_url(&cfg.sso_authority)?;
|
validate_internal_sso_issuer_url(&cfg.sso_authority)?;
|
||||||
validate_internal_sso_redirect_url(&cfg.sso_callback_path)?;
|
validate_internal_sso_redirect_url(&cfg.sso_callback_path)?;
|
||||||
validate_sso_master_password_policy(&cfg.sso_master_password_policy)?;
|
validate_sso_master_password_policy(cfg.sso_master_password_policy.as_ref())?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg._enable_yubico {
|
if cfg._enable_yubico {
|
||||||
@@ -1271,7 +1271,7 @@ fn validate_internal_sso_redirect_url(sso_callback_path: &String) -> Result<open
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn validate_sso_master_password_policy(
|
fn validate_sso_master_password_policy(
|
||||||
sso_master_password_policy: &Option<String>,
|
sso_master_password_policy: Option<&String>,
|
||||||
) -> Result<Option<serde_json::Value>, Error> {
|
) -> Result<Option<serde_json::Value>, Error> {
|
||||||
let policy = sso_master_password_policy.as_ref().map(|mpp| serde_json::from_str::<serde_json::Value>(mpp));
|
let policy = sso_master_password_policy.as_ref().map(|mpp| serde_json::from_str::<serde_json::Value>(mpp));
|
||||||
|
|
||||||
@@ -1725,7 +1725,7 @@ impl Config {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn sso_master_password_policy_value(&self) -> Option<serde_json::Value> {
|
pub fn sso_master_password_policy_value(&self) -> Option<serde_json::Value> {
|
||||||
validate_sso_master_password_policy(&self.sso_master_password_policy()).ok().flatten()
|
validate_sso_master_password_policy(self.sso_master_password_policy().as_ref()).ok().flatten()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sso_scopes_vec(&self) -> Vec<String> {
|
pub fn sso_scopes_vec(&self) -> Vec<String> {
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ impl Attachment {
|
|||||||
let token = encode_jwt(&generate_file_download_claims(self.cipher_uuid.clone(), self.id.clone()));
|
let token = encode_jwt(&generate_file_download_claims(self.cipher_uuid.clone(), self.id.clone()));
|
||||||
Ok(format!("{host}/attachments/{}/{}?token={token}", self.cipher_uuid, self.id))
|
Ok(format!("{host}/attachments/{}/{}?token={token}", self.cipher_uuid, self.id))
|
||||||
} else {
|
} else {
|
||||||
Ok(operator.presign_read(&self.get_file_path(), Duration::from_secs(5 * 60)).await?.uri().to_string())
|
Ok(operator.presign_read(&self.get_file_path(), Duration::from_mins(5)).await?.uri().to_string())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ pub struct Device {
|
|||||||
pub user_uuid: UserId,
|
pub user_uuid: UserId,
|
||||||
|
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub atype: i32, // https://github.com/bitwarden/server/blob/9ebe16587175b1c0e9208f84397bb75d0d595510/src/Core/Enums/DeviceType.cs
|
pub atype: i32, // https://github.com/bitwarden/server/blob/8d547dcc280babab70dd4a3c94ced6a34b12dfbf/src/Core/Enums/DeviceType.cs
|
||||||
pub push_uuid: Option<PushId>,
|
pub push_uuid: Option<PushId>,
|
||||||
pub push_token: Option<String>,
|
pub push_token: Option<String>,
|
||||||
|
|
||||||
@@ -332,6 +332,8 @@ pub enum DeviceType {
|
|||||||
MacOsCLI = 24,
|
MacOsCLI = 24,
|
||||||
#[display("Linux CLI")]
|
#[display("Linux CLI")]
|
||||||
LinuxCLI = 25,
|
LinuxCLI = 25,
|
||||||
|
#[display("DuckDuckGo")]
|
||||||
|
DuckDuckGoBrowser = 26,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DeviceType {
|
impl DeviceType {
|
||||||
@@ -363,6 +365,7 @@ impl DeviceType {
|
|||||||
23 => DeviceType::WindowsCLI,
|
23 => DeviceType::WindowsCLI,
|
||||||
24 => DeviceType::MacOsCLI,
|
24 => DeviceType::MacOsCLI,
|
||||||
25 => DeviceType::LinuxCLI,
|
25 => DeviceType::LinuxCLI,
|
||||||
|
26 => DeviceType::DuckDuckGoBrowser,
|
||||||
_ => DeviceType::UnknownBrowser,
|
_ => DeviceType::UnknownBrowser,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+24
-21
@@ -6,7 +6,7 @@ use std::{
|
|||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
use hickory_resolver::{name_server::TokioConnectionProvider, TokioResolver};
|
use hickory_resolver::{net::runtime::TokioRuntimeProvider, TokioResolver};
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use reqwest::{
|
use reqwest::{
|
||||||
dns::{Name, Resolve, Resolving},
|
dns::{Name, Resolve, Resolving},
|
||||||
@@ -184,35 +184,35 @@ impl CustomDnsResolver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn new() -> Arc<Self> {
|
fn new() -> Arc<Self> {
|
||||||
match TokioResolver::builder(TokioConnectionProvider::default()) {
|
TokioResolver::builder(TokioRuntimeProvider::default())
|
||||||
Ok(mut builder) => {
|
.and_then(|mut builder| {
|
||||||
if CONFIG.dns_prefer_ipv6() {
|
// Hickory's default since v0.26 is `Ipv6AndIpv4`, which sorts IPv6 first
|
||||||
builder.options_mut().ip_strategy = hickory_resolver::config::LookupIpStrategy::Ipv6thenIpv4;
|
// This might cause issues on IPv4 only systems or containers
|
||||||
|
// Unless someone enabled DNS_PREFER_IPV6, use Ipv4AndIpv6, which returns IPv4 first which was our previous default
|
||||||
|
if !CONFIG.dns_prefer_ipv6() {
|
||||||
|
builder.options_mut().ip_strategy = hickory_resolver::config::LookupIpStrategy::Ipv4AndIpv6;
|
||||||
}
|
}
|
||||||
let resolver = builder.build();
|
builder.build()
|
||||||
Arc::new(Self::Hickory(Arc::new(resolver)))
|
})
|
||||||
}
|
.inspect_err(|e| warn!("Error creating Hickory resolver, falling back to default: {e:?}"))
|
||||||
Err(e) => {
|
.map(|resolver| Arc::new(Self::Hickory(Arc::new(resolver))))
|
||||||
warn!("Error creating Hickory resolver, falling back to default: {e:?}");
|
.unwrap_or_else(|_| Arc::new(Self::Default()))
|
||||||
Arc::new(Self::Default())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note that we get an iterator of addresses, but we only grab the first one for convenience
|
// Note that we get an iterator of addresses, but we only grab the first one for convenience
|
||||||
async fn resolve_domain(&self, name: &str) -> Result<Option<SocketAddr>, BoxError> {
|
async fn resolve_domain(&self, name: &str) -> Result<Vec<SocketAddr>, BoxError> {
|
||||||
pre_resolve(name)?;
|
pre_resolve(name)?;
|
||||||
|
|
||||||
let result = match self {
|
let results: Vec<SocketAddr> = match self {
|
||||||
Self::Default() => tokio::net::lookup_host(name).await?.next(),
|
Self::Default() => tokio::net::lookup_host((name, 0)).await?.collect(),
|
||||||
Self::Hickory(r) => r.lookup_ip(name).await?.iter().next().map(|a| SocketAddr::new(a, 0)),
|
Self::Hickory(r) => r.lookup_ip(name).await?.iter().map(|i| SocketAddr::new(i, 0)).collect(),
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(addr) = &result {
|
for addr in &results {
|
||||||
post_resolve(name, addr.ip())?;
|
post_resolve(name, addr.ip())?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(result)
|
Ok(results)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -242,8 +242,11 @@ impl Resolve for CustomDnsResolver {
|
|||||||
let this = self.clone();
|
let this = self.clone();
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
let name = name.as_str();
|
let name = name.as_str();
|
||||||
let result = this.resolve_domain(name).await?;
|
let results = this.resolve_domain(name).await?;
|
||||||
Ok::<reqwest::dns::Addrs, _>(Box::new(result.into_iter()))
|
if results.is_empty() {
|
||||||
|
warn!("Unable to resolve {name} to any valid IP address");
|
||||||
|
}
|
||||||
|
Ok::<reqwest::dns::Addrs, _>(Box::new(results.into_iter()))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -17,7 +17,7 @@ use crate::{
|
|||||||
CONFIG,
|
CONFIG,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub static FAKE_IDENTIFIER: &str = "VW_DUMMY_IDENTIFIER_FOR_OIDC";
|
pub static FAKE_SSO_IDENTIFIER: &str = "00000000-01DC-01DC-01DC-000000000000";
|
||||||
|
|
||||||
static SSO_JWT_ISSUER: LazyLock<String> = LazyLock::new(|| format!("{}|sso", CONFIG.domain_origin()));
|
static SSO_JWT_ISSUER: LazyLock<String> = LazyLock::new(|| format!("{}|sso", CONFIG.domain_origin()));
|
||||||
|
|
||||||
@@ -283,7 +283,7 @@ pub async fn exchange_code(
|
|||||||
|
|
||||||
let email_verified = id_claims.email_verified().or(user_info.email_verified());
|
let email_verified = id_claims.email_verified().or(user_info.email_verified());
|
||||||
|
|
||||||
let user_name = id_claims.preferred_username().map(|un| un.to_string());
|
let user_name = id_claims.preferred_username().or(user_info.preferred_username()).map(|un| un.to_string());
|
||||||
|
|
||||||
let refresh_token = token_response.refresh_token().map(|t| t.secret());
|
let refresh_token = token_response.refresh_token().map(|t| t.secret());
|
||||||
if refresh_token.is_none() && CONFIG.sso_scopes_vec().contains(&"offline_access".to_string()) {
|
if refresh_token.is_none() && CONFIG.sso_scopes_vec().contains(&"offline_access".to_string()) {
|
||||||
|
|||||||
@@ -137,6 +137,14 @@ bit-nav-logo bit-nav-item .bwi-shield {
|
|||||||
app-user-layout app-danger-zone button:nth-child(1) {
|
app-user-layout app-danger-zone button:nth-child(1) {
|
||||||
@extend %vw-hide;
|
@extend %vw-hide;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Hide unsupported Forwarding email alias options */
|
||||||
|
ng-dropdown-panel div.ng-dropdown-panel-items div:has(> [title="Firefox Relay"]) {
|
||||||
|
@extend %vw-hide;
|
||||||
|
}
|
||||||
|
ng-dropdown-panel div.ng-dropdown-panel-items div:has(> [title="DuckDuckGo"]) {
|
||||||
|
@extend %vw-hide;
|
||||||
|
}
|
||||||
/**** END Static Vaultwarden Changes ****/
|
/**** END Static Vaultwarden Changes ****/
|
||||||
/**** START Dynamic Vaultwarden Changes ****/
|
/**** START Dynamic Vaultwarden Changes ****/
|
||||||
{{#if signup_disabled}}
|
{{#if signup_disabled}}
|
||||||
|
|||||||
+1
-1
@@ -734,7 +734,7 @@ where
|
|||||||
|
|
||||||
warn!("Can't connect to database, retrying: {e:?}");
|
warn!("Can't connect to database, retrying: {e:?}");
|
||||||
|
|
||||||
sleep(Duration::from_millis(1_000)).await;
|
sleep(Duration::from_secs(1)).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user