mirror of
https://github.com/dani-garcia/vaultwarden.git
synced 2026-03-24 02:39:21 -07:00
Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
235cf88231 | ||
|
|
c0a78dd55a | ||
|
|
711bb53d3d | ||
|
|
650defac75 | ||
|
|
2b3736802d | ||
|
|
9c7df6412c | ||
|
|
065c1f2cd5 | ||
|
|
1a1d7f578a | ||
|
|
2b16a05e54 | ||
|
|
c6e9948984 | ||
|
|
ecdb18fcde | ||
|
|
df25d316d6 | ||
|
|
747286dccd | ||
|
|
e60105411b | ||
|
|
937857a0bc | ||
|
|
ba55191676 |
@@ -372,16 +372,22 @@
|
||||
## Note that clients cache the /api/config endpoint for about 1 hour and it could take some time before they are enabled or disabled!
|
||||
##
|
||||
## The following flags are available:
|
||||
## - "inline-menu-positioning-improvements": Enable the use of inline menu password generator and identity suggestions in the browser extension.
|
||||
## - "inline-menu-totp": Enable the use of inline menu TOTP codes in the browser extension.
|
||||
## - "ssh-agent": Enable SSH agent support on Desktop. (Needs desktop >=2024.12.0)
|
||||
## - "ssh-key-vault-item": Enable the creation and use of SSH key vault items. (Needs clients >=2024.12.0)
|
||||
## - "pm-25373-windows-biometrics-v2": Enable the new implementation of biometrics on Windows. (Needs desktop >= 2025.11.0)
|
||||
## - "export-attachments": Enable support for exporting attachments (Clients >=2025.4.0)
|
||||
## - "anon-addy-self-host-alias": Enable configuring self-hosted Anon Addy alias generator. (Needs Android >=2025.3.0, iOS >=2025.4.0)
|
||||
## - "simple-login-self-host-alias": Enable configuring self-hosted Simple Login alias generator. (Needs Android >=2025.3.0, iOS >=2025.4.0)
|
||||
## - "mutual-tls": Enable the use of mutual TLS on Android (Client >= 2025.2.0)
|
||||
# EXPERIMENTAL_CLIENT_FEATURE_FLAGS=fido2-vault-credentials
|
||||
## - "pm-5594-safari-account-switching": Enable account switching in Safari. (Safari >= 2026.2.0)
|
||||
## - "ssh-agent": Enable SSH agent support on Desktop. (Desktop >= 2024.12.0)
|
||||
## - "ssh-agent-v2": Enable newer SSH agent support. (Desktop >= 2026.2.1)
|
||||
## - "ssh-key-vault-item": Enable the creation and use of SSH key vault items. (Clients >= 2024.12.0)
|
||||
## - "pm-25373-windows-biometrics-v2": Enable the new implementation of biometrics on Windows. (Desktop >= 2025.11.0)
|
||||
## - "anon-addy-self-host-alias": Enable configuring self-hosted Anon Addy alias generator. (Android >= 2025.3.0, iOS >= 2025.4.0)
|
||||
## - "simple-login-self-host-alias": Enable configuring self-hosted Simple Login alias generator. (Android >= 2025.3.0, iOS >= 2025.4.0)
|
||||
## - "mutual-tls": Enable the use of mutual TLS on Android (Clients >= 2025.2.0)
|
||||
## - "cxp-import-mobile": Enable the import via CXP on iOS (Clients >= 2025.9.2)
|
||||
## - "cxp-export-mobile": Enable the export via CXP on iOS (Clients >= 2025.9.2)
|
||||
## - "pm-30529-webauthn-related-origins":
|
||||
## - "desktop-ui-migration-milestone-1": Special feature flag for desktop UI (Desktop >= 2026.2.0)
|
||||
## - "desktop-ui-migration-milestone-2": Special feature flag for desktop UI (Desktop >= 2026.2.0)
|
||||
## - "desktop-ui-migration-milestone-3": Special feature flag for desktop UI (Desktop >= 2026.2.0)
|
||||
## - "desktop-ui-migration-milestone-4": Special feature flag for desktop UI (Desktop >= 2026.2.0)
|
||||
# EXPERIMENTAL_CLIENT_FEATURE_FLAGS=
|
||||
|
||||
## Require new device emails. When a user logs in an email is required to be sent.
|
||||
## If sending the email fails the login attempt will fail!!
|
||||
|
||||
37
.github/workflows/build.yml
vendored
37
.github/workflows/build.yml
vendored
@@ -62,7 +62,7 @@ jobs:
|
||||
|
||||
# Checkout the repo
|
||||
- name: "Checkout"
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 #v6.0.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
@@ -85,32 +85,23 @@ jobs:
|
||||
# End Determine rust-toolchain version
|
||||
|
||||
|
||||
# Only install the clippy and rustfmt components on the default rust-toolchain
|
||||
- name: "Install rust-toolchain version"
|
||||
uses: dtolnay/rust-toolchain@efa25f7f19611383d5b0ccf2d1c8914531636bf9 # master @ Feb 13, 2026, 3:46 AM GMT+1
|
||||
if: ${{ matrix.channel == 'rust-toolchain' }}
|
||||
with:
|
||||
toolchain: "${{steps.toolchain.outputs.RUST_TOOLCHAIN}}"
|
||||
components: clippy, rustfmt
|
||||
# End Uses the rust-toolchain file to determine version
|
||||
|
||||
|
||||
# Install the any other channel to be used for which we do not execute clippy and rustfmt
|
||||
- name: "Install MSRV version"
|
||||
uses: dtolnay/rust-toolchain@efa25f7f19611383d5b0ccf2d1c8914531636bf9 # master @ Feb 13, 2026, 3:46 AM GMT+1
|
||||
if: ${{ matrix.channel != 'rust-toolchain' }}
|
||||
with:
|
||||
toolchain: "${{steps.toolchain.outputs.RUST_TOOLCHAIN}}"
|
||||
# End Install the MSRV channel to be used
|
||||
|
||||
# Set the current matrix toolchain version as default
|
||||
- name: "Set toolchain ${{steps.toolchain.outputs.RUST_TOOLCHAIN}} as default"
|
||||
- name: "Install toolchain ${{steps.toolchain.outputs.RUST_TOOLCHAIN}} as default"
|
||||
env:
|
||||
CHANNEL: ${{ matrix.channel }}
|
||||
RUST_TOOLCHAIN: ${{steps.toolchain.outputs.RUST_TOOLCHAIN}}
|
||||
run: |
|
||||
# Remove the rust-toolchain.toml
|
||||
rm rust-toolchain.toml
|
||||
# Set the default
|
||||
|
||||
# Install the correct toolchain version
|
||||
rustup toolchain install "${RUST_TOOLCHAIN}" --profile minimal --no-self-update
|
||||
|
||||
# If this matrix is the `rust-toolchain` flow, also install rustfmt and clippy
|
||||
if [[ "${CHANNEL}" == 'rust-toolchain' ]]; then
|
||||
rustup component add --toolchain "${RUST_TOOLCHAIN}" rustfmt clippy
|
||||
fi
|
||||
|
||||
# Set as the default toolchain
|
||||
rustup default "${RUST_TOOLCHAIN}"
|
||||
|
||||
# Show environment
|
||||
@@ -122,7 +113,7 @@ jobs:
|
||||
|
||||
# Enable Rust Caching
|
||||
- name: Rust Caching
|
||||
uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
|
||||
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
|
||||
with:
|
||||
# Use a custom prefix-key to force a fresh start. This is sometimes needed with bigger changes.
|
||||
# Like changing the build host from Ubuntu 20.04 to 22.04 for example.
|
||||
|
||||
2
.github/workflows/check-templates.yml
vendored
2
.github/workflows/check-templates.yml
vendored
@@ -20,7 +20,7 @@ jobs:
|
||||
steps:
|
||||
# Checkout the repo
|
||||
- name: "Checkout"
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 #v6.0.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
# End Checkout the repo
|
||||
|
||||
4
.github/workflows/hadolint.yml
vendored
4
.github/workflows/hadolint.yml
vendored
@@ -20,7 +20,7 @@ jobs:
|
||||
steps:
|
||||
# Start Docker Buildx
|
||||
- name: Setup Docker Buildx
|
||||
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
|
||||
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
|
||||
# https://github.com/moby/buildkit/issues/3969
|
||||
# Also set max parallelism to 2, the default of 4 breaks GitHub Actions and causes OOMKills
|
||||
with:
|
||||
@@ -40,7 +40,7 @@ jobs:
|
||||
# End Download hadolint
|
||||
# Checkout the repo
|
||||
- name: Checkout
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 #v6.0.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
# End Checkout the repo
|
||||
|
||||
93
.github/workflows/release.yml
vendored
93
.github/workflows/release.yml
vendored
@@ -20,23 +20,25 @@ defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
env:
|
||||
# The *_REPO variables need to be configured as repository variables
|
||||
# Append `/settings/variables/actions` to your repo url
|
||||
# DOCKERHUB_REPO needs to be 'index.docker.io/<user>/<repo>'
|
||||
# Check for Docker hub credentials in secrets
|
||||
HAVE_DOCKERHUB_LOGIN: ${{ vars.DOCKERHUB_REPO != '' && secrets.DOCKERHUB_USERNAME != '' && secrets.DOCKERHUB_TOKEN != '' }}
|
||||
# GHCR_REPO needs to be 'ghcr.io/<user>/<repo>'
|
||||
# Check for Github credentials in secrets
|
||||
HAVE_GHCR_LOGIN: ${{ vars.GHCR_REPO != '' && github.repository_owner != '' && secrets.GITHUB_TOKEN != '' }}
|
||||
# QUAY_REPO needs to be 'quay.io/<user>/<repo>'
|
||||
# Check for Quay.io credentials in secrets
|
||||
HAVE_QUAY_LOGIN: ${{ vars.QUAY_REPO != '' && secrets.QUAY_USERNAME != '' && secrets.QUAY_TOKEN != '' }}
|
||||
# A "release" environment must be created in the repository settings
|
||||
# (Settings > Environments > New environment) with the following
|
||||
# variables and secrets configured as needed.
|
||||
#
|
||||
# Variables (only set the ones for registries you want to push to):
|
||||
# DOCKERHUB_REPO: 'index.docker.io/<user>/<repo>'
|
||||
# QUAY_REPO: 'quay.io/<user>/<repo>'
|
||||
# GHCR_REPO: 'ghcr.io/<user>/<repo>'
|
||||
#
|
||||
# Secrets (only required when the corresponding *_REPO variable is set):
|
||||
# DOCKERHUB_REPO => DOCKERHUB_USERNAME, DOCKERHUB_TOKEN
|
||||
# QUAY_REPO => QUAY_USERNAME, QUAY_TOKEN
|
||||
# GITHUB_TOKEN is provided automatically
|
||||
|
||||
jobs:
|
||||
docker-build:
|
||||
name: Build Vaultwarden containers
|
||||
if: ${{ github.repository == 'dani-garcia/vaultwarden' }}
|
||||
environment: release
|
||||
permissions:
|
||||
packages: write # Needed to upload packages and artifacts
|
||||
contents: read
|
||||
@@ -54,13 +56,13 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Initialize QEMU binfmt support
|
||||
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0
|
||||
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0
|
||||
with:
|
||||
platforms: "arm64,arm"
|
||||
|
||||
# Start Docker Buildx
|
||||
- name: Setup Docker Buildx
|
||||
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
|
||||
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
|
||||
# https://github.com/moby/buildkit/issues/3969
|
||||
# Also set max parallelism to 2, the default of 4 breaks GitHub Actions and causes OOMKills
|
||||
with:
|
||||
@@ -73,7 +75,7 @@ jobs:
|
||||
|
||||
# Checkout the repo
|
||||
- name: Checkout
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 #v6.0.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
# We need fetch-depth of 0 so we also get all the tag metadata
|
||||
with:
|
||||
persist-credentials: false
|
||||
@@ -102,14 +104,14 @@ jobs:
|
||||
|
||||
# Login to Docker Hub
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
|
||||
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
if: ${{ env.HAVE_DOCKERHUB_LOGIN == 'true' }}
|
||||
if: ${{ vars.DOCKERHUB_REPO != '' }}
|
||||
|
||||
- name: Add registry for DockerHub
|
||||
if: ${{ env.HAVE_DOCKERHUB_LOGIN == 'true' }}
|
||||
if: ${{ vars.DOCKERHUB_REPO != '' }}
|
||||
env:
|
||||
DOCKERHUB_REPO: ${{ vars.DOCKERHUB_REPO }}
|
||||
run: |
|
||||
@@ -117,15 +119,15 @@ jobs:
|
||||
|
||||
# Login to GitHub Container Registry
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
|
||||
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
if: ${{ env.HAVE_GHCR_LOGIN == 'true' }}
|
||||
if: ${{ vars.GHCR_REPO != '' }}
|
||||
|
||||
- name: Add registry for ghcr.io
|
||||
if: ${{ env.HAVE_GHCR_LOGIN == 'true' }}
|
||||
if: ${{ vars.GHCR_REPO != '' }}
|
||||
env:
|
||||
GHCR_REPO: ${{ vars.GHCR_REPO }}
|
||||
run: |
|
||||
@@ -133,15 +135,15 @@ jobs:
|
||||
|
||||
# Login to Quay.io
|
||||
- name: Login to Quay.io
|
||||
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
|
||||
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
|
||||
with:
|
||||
registry: quay.io
|
||||
username: ${{ secrets.QUAY_USERNAME }}
|
||||
password: ${{ secrets.QUAY_TOKEN }}
|
||||
if: ${{ env.HAVE_QUAY_LOGIN == 'true' }}
|
||||
if: ${{ vars.QUAY_REPO != '' }}
|
||||
|
||||
- name: Add registry for Quay.io
|
||||
if: ${{ env.HAVE_QUAY_LOGIN == 'true' }}
|
||||
if: ${{ vars.QUAY_REPO != '' }}
|
||||
env:
|
||||
QUAY_REPO: ${{ vars.QUAY_REPO }}
|
||||
run: |
|
||||
@@ -155,7 +157,7 @@ jobs:
|
||||
run: |
|
||||
#
|
||||
# Check if there is a GitHub Container Registry Login and use it for caching
|
||||
if [[ -n "${HAVE_GHCR_LOGIN}" ]]; then
|
||||
if [[ -n "${GHCR_REPO}" ]]; then
|
||||
echo "BAKE_CACHE_FROM=type=registry,ref=${GHCR_REPO}-buildcache:${BASE_IMAGE}-${NORMALIZED_ARCH}" | tee -a "${GITHUB_ENV}"
|
||||
echo "BAKE_CACHE_TO=type=registry,ref=${GHCR_REPO}-buildcache:${BASE_IMAGE}-${NORMALIZED_ARCH},compression=zstd,mode=max" | tee -a "${GITHUB_ENV}"
|
||||
else
|
||||
@@ -181,7 +183,7 @@ jobs:
|
||||
|
||||
- name: Bake ${{ matrix.base_image }} containers
|
||||
id: bake_vw
|
||||
uses: docker/bake-action@5be5f02ff8819ecd3092ea6b2e6261c31774f2b4 # v6.10.0
|
||||
uses: docker/bake-action@82490499d2e5613fcead7e128237ef0b0ea210f7 # v7.0.0
|
||||
env:
|
||||
BASE_TAGS: "${{ steps.determine-version.outputs.BASE_TAGS }}"
|
||||
SOURCE_COMMIT: "${{ env.SOURCE_COMMIT }}"
|
||||
@@ -218,7 +220,7 @@ jobs:
|
||||
touch "${RUNNER_TEMP}/digests/${digest#sha256:}"
|
||||
|
||||
- name: Upload digest
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: digests-${{ env.NORMALIZED_ARCH }}-${{ matrix.base_image }}
|
||||
path: ${{ runner.temp }}/digests/*
|
||||
@@ -233,12 +235,12 @@ jobs:
|
||||
|
||||
# Upload artifacts to Github Actions and Attest the binaries
|
||||
- name: Attest binaries
|
||||
uses: actions/attest-build-provenance@96278af6caaf10aea03fd8d33a09a777ca52d62f # v3.2.0
|
||||
uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0
|
||||
with:
|
||||
subject-path: vaultwarden-${{ env.NORMALIZED_ARCH }}
|
||||
|
||||
- name: Upload binaries as artifacts
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: vaultwarden-${{ env.SOURCE_VERSION }}-linux-${{ env.NORMALIZED_ARCH }}-${{ matrix.base_image }}
|
||||
path: vaultwarden-${{ env.NORMALIZED_ARCH }}
|
||||
@@ -247,6 +249,7 @@ jobs:
|
||||
name: Merge manifests
|
||||
runs-on: ubuntu-latest
|
||||
needs: docker-build
|
||||
environment: release
|
||||
permissions:
|
||||
packages: write # Needed to upload packages and artifacts
|
||||
attestations: write # Needed to generate an artifact attestation for a build
|
||||
@@ -257,7 +260,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Download digests
|
||||
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
path: ${{ runner.temp }}/digests
|
||||
pattern: digests-*-${{ matrix.base_image }}
|
||||
@@ -265,14 +268,14 @@ jobs:
|
||||
|
||||
# Login to Docker Hub
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
|
||||
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
if: ${{ env.HAVE_DOCKERHUB_LOGIN == 'true' }}
|
||||
if: ${{ vars.DOCKERHUB_REPO != '' }}
|
||||
|
||||
- name: Add registry for DockerHub
|
||||
if: ${{ env.HAVE_DOCKERHUB_LOGIN == 'true' }}
|
||||
if: ${{ vars.DOCKERHUB_REPO != '' }}
|
||||
env:
|
||||
DOCKERHUB_REPO: ${{ vars.DOCKERHUB_REPO }}
|
||||
run: |
|
||||
@@ -280,15 +283,15 @@ jobs:
|
||||
|
||||
# Login to GitHub Container Registry
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
|
||||
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
if: ${{ env.HAVE_GHCR_LOGIN == 'true' }}
|
||||
if: ${{ vars.GHCR_REPO != '' }}
|
||||
|
||||
- name: Add registry for ghcr.io
|
||||
if: ${{ env.HAVE_GHCR_LOGIN == 'true' }}
|
||||
if: ${{ vars.GHCR_REPO != '' }}
|
||||
env:
|
||||
GHCR_REPO: ${{ vars.GHCR_REPO }}
|
||||
run: |
|
||||
@@ -296,15 +299,15 @@ jobs:
|
||||
|
||||
# Login to Quay.io
|
||||
- name: Login to Quay.io
|
||||
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
|
||||
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
|
||||
with:
|
||||
registry: quay.io
|
||||
username: ${{ secrets.QUAY_USERNAME }}
|
||||
password: ${{ secrets.QUAY_TOKEN }}
|
||||
if: ${{ env.HAVE_QUAY_LOGIN == 'true' }}
|
||||
if: ${{ vars.QUAY_REPO != '' }}
|
||||
|
||||
- name: Add registry for Quay.io
|
||||
if: ${{ env.HAVE_QUAY_LOGIN == 'true' }}
|
||||
if: ${{ vars.QUAY_REPO != '' }}
|
||||
env:
|
||||
QUAY_REPO: ${{ vars.QUAY_REPO }}
|
||||
run: |
|
||||
@@ -357,24 +360,24 @@ jobs:
|
||||
|
||||
# Attest container images
|
||||
- name: Attest - docker.io - ${{ matrix.base_image }}
|
||||
if: ${{ env.HAVE_DOCKERHUB_LOGIN == 'true' && env.DIGEST_SHA != ''}}
|
||||
uses: actions/attest-build-provenance@96278af6caaf10aea03fd8d33a09a777ca52d62f # v3.2.0
|
||||
if: ${{ vars.DOCKERHUB_REPO != '' && env.DIGEST_SHA != ''}}
|
||||
uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0
|
||||
with:
|
||||
subject-name: ${{ vars.DOCKERHUB_REPO }}
|
||||
subject-digest: ${{ env.DIGEST_SHA }}
|
||||
push-to-registry: true
|
||||
|
||||
- name: Attest - ghcr.io - ${{ matrix.base_image }}
|
||||
if: ${{ env.HAVE_GHCR_LOGIN == 'true' && env.DIGEST_SHA != ''}}
|
||||
uses: actions/attest-build-provenance@96278af6caaf10aea03fd8d33a09a777ca52d62f # v3.2.0
|
||||
if: ${{ vars.GHCR_REPO != '' && env.DIGEST_SHA != ''}}
|
||||
uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0
|
||||
with:
|
||||
subject-name: ${{ vars.GHCR_REPO }}
|
||||
subject-digest: ${{ env.DIGEST_SHA }}
|
||||
push-to-registry: true
|
||||
|
||||
- name: Attest - quay.io - ${{ matrix.base_image }}
|
||||
if: ${{ env.HAVE_QUAY_LOGIN == 'true' && env.DIGEST_SHA != ''}}
|
||||
uses: actions/attest-build-provenance@96278af6caaf10aea03fd8d33a09a777ca52d62f # v3.2.0
|
||||
if: ${{ vars.QUAY_REPO != '' && env.DIGEST_SHA != ''}}
|
||||
uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0
|
||||
with:
|
||||
subject-name: ${{ vars.QUAY_REPO }}
|
||||
subject-digest: ${{ env.DIGEST_SHA }}
|
||||
|
||||
6
.github/workflows/trivy.yml
vendored
6
.github/workflows/trivy.yml
vendored
@@ -33,12 +33,12 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 #v6.0.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Run Trivy vulnerability scanner
|
||||
uses: aquasecurity/trivy-action@c1824fd6edce30d7ab345a9989de00bbd46ef284 # 0.34.0
|
||||
uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # v0.35.0
|
||||
env:
|
||||
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
|
||||
@@ -50,6 +50,6 @@ jobs:
|
||||
severity: CRITICAL,HIGH
|
||||
|
||||
- name: Upload Trivy scan results to GitHub Security tab
|
||||
uses: github/codeql-action/upload-sarif@9e907b5e64f6b83e7804b09294d44122997950d6 # v4.32.3
|
||||
uses: github/codeql-action/upload-sarif@38697555549f1db7851b81482ff19f1fa5c4fedc # v4.34.1
|
||||
with:
|
||||
sarif_file: 'trivy-results.sarif'
|
||||
|
||||
4
.github/workflows/typos.yml
vendored
4
.github/workflows/typos.yml
vendored
@@ -16,11 +16,11 @@ jobs:
|
||||
steps:
|
||||
# Checkout the repo
|
||||
- name: Checkout
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 #v6.0.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
# End Checkout the repo
|
||||
|
||||
# When this version is updated, do not forget to update this in `.pre-commit-config.yaml` too
|
||||
- name: Spell Check Repo
|
||||
uses: crate-ci/typos@57b11c6b7e54c402ccd9cda953f1072ec4f78e33 # v1.43.5
|
||||
uses: crate-ci/typos@631208b7aac2daa8b707f55e7331f9112b0e062d # v1.44.0
|
||||
|
||||
4
.github/workflows/zizmor.yml
vendored
4
.github/workflows/zizmor.yml
vendored
@@ -19,12 +19,12 @@ jobs:
|
||||
security-events: write # To write the security report
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 #v6.0.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Run zizmor
|
||||
uses: zizmorcore/zizmor-action@0dce2577a4760a2749d8cfb7a84b7d5585ebcb7d # v0.5.0
|
||||
uses: zizmorcore/zizmor-action@71321a20a9ded102f6e9ce5718a2fcec2c4f70d8 # v0.5.2
|
||||
with:
|
||||
# intentionally not scanning the entire repository,
|
||||
# since it contains integration tests.
|
||||
|
||||
@@ -53,6 +53,6 @@ repos:
|
||||
- "cd docker && make"
|
||||
# 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: 57b11c6b7e54c402ccd9cda953f1072ec4f78e33 # v1.43.5
|
||||
rev: 631208b7aac2daa8b707f55e7331f9112b0e062d # v1.44.0
|
||||
hooks:
|
||||
- id: typos
|
||||
|
||||
532
Cargo.lock
generated
532
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
28
Cargo.toml
28
Cargo.toml
@@ -1,6 +1,6 @@
|
||||
[workspace.package]
|
||||
edition = "2021"
|
||||
rust-version = "1.91.0"
|
||||
rust-version = "1.92.0"
|
||||
license = "AGPL-3.0-only"
|
||||
repository = "https://github.com/dani-garcia/vaultwarden"
|
||||
publish = false
|
||||
@@ -79,7 +79,7 @@ dashmap = "6.1.0"
|
||||
|
||||
# Async futures
|
||||
futures = "0.3.32"
|
||||
tokio = { version = "1.49.0", features = ["rt-multi-thread", "fs", "io-util", "parking_lot", "time", "signal", "net"] }
|
||||
tokio = { version = "1.50.0", features = ["rt-multi-thread", "fs", "io-util", "parking_lot", "time", "signal", "net"] }
|
||||
tokio-util = { version = "0.7.18", features = ["compat"]}
|
||||
|
||||
# A generic serialization/deserialization framework
|
||||
@@ -88,14 +88,14 @@ serde_json = "1.0.149"
|
||||
|
||||
# A safe, extensible ORM and Query builder
|
||||
# Currently pinned diesel to v2.3.3 as newer version break MySQL/MariaDB compatibility
|
||||
diesel = { version = "2.3.6", features = ["chrono", "r2d2", "numeric"] }
|
||||
diesel = { version = "2.3.7", features = ["chrono", "r2d2", "numeric"] }
|
||||
diesel_migrations = "2.3.1"
|
||||
|
||||
derive_more = { version = "2.1.1", features = ["from", "into", "as_ref", "deref", "display"] }
|
||||
diesel-derive-newtype = "2.1.2"
|
||||
|
||||
# Bundled/Static SQLite
|
||||
libsqlite3-sys = { version = "0.35.0", features = ["bundled"], optional = true }
|
||||
libsqlite3-sys = { version = "0.36.0", features = ["bundled"], optional = true }
|
||||
|
||||
# Crypto-related libraries
|
||||
rand = "0.10.0"
|
||||
@@ -103,10 +103,10 @@ ring = "0.17.14"
|
||||
subtle = "2.6.1"
|
||||
|
||||
# UUID generation
|
||||
uuid = { version = "1.21.0", features = ["v4"] }
|
||||
uuid = { version = "1.22.0", features = ["v4"] }
|
||||
|
||||
# Date and time libraries
|
||||
chrono = { version = "0.4.43", features = ["clock", "serde"], default-features = false }
|
||||
chrono = { version = "0.4.44", features = ["clock", "serde"], default-features = false }
|
||||
chrono-tz = "0.10.4"
|
||||
time = "0.3.47"
|
||||
|
||||
@@ -155,14 +155,14 @@ bytes = "1.11.1"
|
||||
svg-hush = "0.9.6"
|
||||
|
||||
# Cache function results (Used for version check and favicon fetching)
|
||||
cached = { version = "0.56.0", features = ["async"] }
|
||||
cached = { version = "0.59.0", features = ["async"] }
|
||||
|
||||
# Used for custom short lived cookie jar during favicon extraction
|
||||
cookie = "0.18.1"
|
||||
cookie_store = "0.22.1"
|
||||
|
||||
# Used by U2F, JWT and PostgreSQL
|
||||
openssl = "0.10.75"
|
||||
openssl = "0.10.76"
|
||||
|
||||
# CLI argument parsing
|
||||
pico-args = "0.5.0"
|
||||
@@ -173,7 +173,7 @@ governor = "0.10.4"
|
||||
|
||||
# OIDC for SSO
|
||||
openidconnect = { version = "4.0.1", features = ["reqwest", "rustls-tls"] }
|
||||
mini-moka = "0.10.3"
|
||||
moka = { version = "0.12.15", features = ["future"] }
|
||||
|
||||
# Check client versions for specific features.
|
||||
semver = "1.0.27"
|
||||
@@ -182,7 +182,7 @@ semver = "1.0.27"
|
||||
# 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 }
|
||||
|
||||
which = "8.0.0"
|
||||
which = "8.0.2"
|
||||
|
||||
# Argon2 library with support for the PHC format
|
||||
argon2 = "0.5.3"
|
||||
@@ -197,10 +197,10 @@ grass_compiler = { version = "0.13.4", default-features = false }
|
||||
opendal = { version = "0.55.0", features = ["services-fs"], default-features = false }
|
||||
|
||||
# For retrieving AWS credentials, including temporary SSO credentials
|
||||
anyhow = { version = "1.0.101", optional = true }
|
||||
aws-config = { version = "1.8.14", features = ["behavior-version-latest", "rt-tokio", "credentials-process", "sso"], default-features = false, optional = true }
|
||||
aws-credential-types = { version = "1.2.13", optional = true }
|
||||
aws-smithy-runtime-api = { version = "1.11.5", 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-credential-types = { version = "1.2.14", optional = true }
|
||||
aws-smithy-runtime-api = { version = "1.11.6", optional = true }
|
||||
http = { version = "1.4.0", optional = true }
|
||||
reqsign = { version = "0.16.5", optional = true }
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
---
|
||||
vault_version: "v2026.1.1"
|
||||
vault_image_digest: "sha256:062fcf0d5dc37247dae61b0ee1ba5d20f9296e290d7ad1f6114ea5888f5738a7"
|
||||
vault_version: "v2026.2.0"
|
||||
vault_image_digest: "sha256:37c8661fa59dcdfbd3baa8366b6e950ef292b15adfeff1f57812b075c1fd3447"
|
||||
# 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
|
||||
# https://github.com/tonistiigi/xx | https://hub.docker.com/r/tonistiigi/xx/tags
|
||||
xx_image_digest: "sha256:c64defb9ed5a91eacb37f96ccc3d4cd72521c4bd18d5442905b95e2226b0e707"
|
||||
rust_version: 1.93.1 # Rust version to be used
|
||||
rust_version: 1.94.0 # Rust version to be used
|
||||
debian_version: trixie # Debian release name to be used
|
||||
alpine_version: "3.23" # Alpine version to be used
|
||||
# For which platforms/architectures will we try to build images
|
||||
|
||||
@@ -19,23 +19,23 @@
|
||||
# - 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.
|
||||
# - From the command line:
|
||||
# $ docker pull docker.io/vaultwarden/web-vault:v2026.1.1
|
||||
# $ docker image inspect --format "{{.RepoDigests}}" docker.io/vaultwarden/web-vault:v2026.1.1
|
||||
# [docker.io/vaultwarden/web-vault@sha256:062fcf0d5dc37247dae61b0ee1ba5d20f9296e290d7ad1f6114ea5888f5738a7]
|
||||
# $ docker pull docker.io/vaultwarden/web-vault:v2026.2.0
|
||||
# $ docker image inspect --format "{{.RepoDigests}}" docker.io/vaultwarden/web-vault:v2026.2.0
|
||||
# [docker.io/vaultwarden/web-vault@sha256:37c8661fa59dcdfbd3baa8366b6e950ef292b15adfeff1f57812b075c1fd3447]
|
||||
#
|
||||
# - Conversely, to get the tag name from the digest:
|
||||
# $ docker image inspect --format "{{.RepoTags}}" docker.io/vaultwarden/web-vault@sha256:062fcf0d5dc37247dae61b0ee1ba5d20f9296e290d7ad1f6114ea5888f5738a7
|
||||
# [docker.io/vaultwarden/web-vault:v2026.1.1]
|
||||
# $ docker image inspect --format "{{.RepoTags}}" docker.io/vaultwarden/web-vault@sha256:37c8661fa59dcdfbd3baa8366b6e950ef292b15adfeff1f57812b075c1fd3447
|
||||
# [docker.io/vaultwarden/web-vault:v2026.2.0]
|
||||
#
|
||||
FROM --platform=linux/amd64 docker.io/vaultwarden/web-vault@sha256:062fcf0d5dc37247dae61b0ee1ba5d20f9296e290d7ad1f6114ea5888f5738a7 AS vault
|
||||
FROM --platform=linux/amd64 docker.io/vaultwarden/web-vault@sha256:37c8661fa59dcdfbd3baa8366b6e950ef292b15adfeff1f57812b075c1fd3447 AS vault
|
||||
|
||||
########################## ALPINE BUILD IMAGES ##########################
|
||||
## 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
|
||||
FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:x86_64-musl-stable-1.93.1 AS build_amd64
|
||||
FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:aarch64-musl-stable-1.93.1 AS build_arm64
|
||||
FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:armv7-musleabihf-stable-1.93.1 AS build_armv7
|
||||
FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:arm-musleabi-stable-1.93.1 AS build_armv6
|
||||
FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:x86_64-musl-stable-1.94.0 AS build_amd64
|
||||
FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:aarch64-musl-stable-1.94.0 AS build_arm64
|
||||
FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:armv7-musleabihf-stable-1.94.0 AS build_armv7
|
||||
FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:arm-musleabi-stable-1.94.0 AS build_armv6
|
||||
|
||||
########################## BUILD IMAGE ##########################
|
||||
# hadolint ignore=DL3006
|
||||
|
||||
@@ -19,15 +19,15 @@
|
||||
# - 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.
|
||||
# - From the command line:
|
||||
# $ docker pull docker.io/vaultwarden/web-vault:v2026.1.1
|
||||
# $ docker image inspect --format "{{.RepoDigests}}" docker.io/vaultwarden/web-vault:v2026.1.1
|
||||
# [docker.io/vaultwarden/web-vault@sha256:062fcf0d5dc37247dae61b0ee1ba5d20f9296e290d7ad1f6114ea5888f5738a7]
|
||||
# $ docker pull docker.io/vaultwarden/web-vault:v2026.2.0
|
||||
# $ docker image inspect --format "{{.RepoDigests}}" docker.io/vaultwarden/web-vault:v2026.2.0
|
||||
# [docker.io/vaultwarden/web-vault@sha256:37c8661fa59dcdfbd3baa8366b6e950ef292b15adfeff1f57812b075c1fd3447]
|
||||
#
|
||||
# - Conversely, to get the tag name from the digest:
|
||||
# $ docker image inspect --format "{{.RepoTags}}" docker.io/vaultwarden/web-vault@sha256:062fcf0d5dc37247dae61b0ee1ba5d20f9296e290d7ad1f6114ea5888f5738a7
|
||||
# [docker.io/vaultwarden/web-vault:v2026.1.1]
|
||||
# $ docker image inspect --format "{{.RepoTags}}" docker.io/vaultwarden/web-vault@sha256:37c8661fa59dcdfbd3baa8366b6e950ef292b15adfeff1f57812b075c1fd3447
|
||||
# [docker.io/vaultwarden/web-vault:v2026.2.0]
|
||||
#
|
||||
FROM --platform=linux/amd64 docker.io/vaultwarden/web-vault@sha256:062fcf0d5dc37247dae61b0ee1ba5d20f9296e290d7ad1f6114ea5888f5738a7 AS vault
|
||||
FROM --platform=linux/amd64 docker.io/vaultwarden/web-vault@sha256:37c8661fa59dcdfbd3baa8366b6e950ef292b15adfeff1f57812b075c1fd3447 AS vault
|
||||
|
||||
########################## Cross Compile Docker Helper 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 ##########################
|
||||
# hadolint ignore=DL3006
|
||||
FROM --platform=$BUILDPLATFORM docker.io/library/rust:1.93.1-slim-trixie AS build
|
||||
FROM --platform=$BUILDPLATFORM docker.io/library/rust:1.94.0-slim-trixie AS build
|
||||
COPY --from=xx / /
|
||||
ARG TARGETARCH
|
||||
ARG TARGETVARIANT
|
||||
|
||||
@@ -13,8 +13,8 @@ path = "src/lib.rs"
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
quote = "1.0.44"
|
||||
syn = "2.0.114"
|
||||
quote = "1.0.45"
|
||||
syn = "2.0.117"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
[toolchain]
|
||||
channel = "1.93.1"
|
||||
channel = "1.94.0"
|
||||
components = [ "rustfmt", "clippy" ]
|
||||
profile = "minimal"
|
||||
|
||||
@@ -32,7 +32,7 @@ use crate::{
|
||||
mail,
|
||||
util::{
|
||||
container_base_image, format_naive_datetime_local, get_active_web_release, get_display_size,
|
||||
is_running_in_container, NumberOrString,
|
||||
is_running_in_container, parse_experimental_client_feature_flags, FeatureFlagFilter, NumberOrString,
|
||||
},
|
||||
CONFIG, VERSION,
|
||||
};
|
||||
@@ -637,7 +637,6 @@ use cached::proc_macro::cached;
|
||||
/// Cache this function to prevent API call rate limit. Github only allows 60 requests per hour, and we use 3 here already
|
||||
/// It will cache this function for 600 seconds (10 minutes) which should prevent the exhaustion of the rate limit
|
||||
/// Any cache will be lost if Vaultwarden is restarted
|
||||
use std::time::Duration; // Needed for cached
|
||||
#[cached(time = 600, sync_writes = "default")]
|
||||
async fn get_release_info(has_http_access: bool) -> (String, String, String) {
|
||||
// If the HTTP Check failed, do not even attempt to check for new versions since we were not able to connect with github.com anyway.
|
||||
@@ -734,6 +733,13 @@ async fn diagnostics(_token: AdminToken, ip_header: IpHeader, conn: DbConn) -> A
|
||||
|
||||
let ip_header_name = &ip_header.0.unwrap_or_default();
|
||||
|
||||
let invalid_feature_flags: Vec<String> = parse_experimental_client_feature_flags(
|
||||
&CONFIG.experimental_client_feature_flags(),
|
||||
FeatureFlagFilter::InvalidOnly,
|
||||
)
|
||||
.into_keys()
|
||||
.collect();
|
||||
|
||||
let diagnostics_json = json!({
|
||||
"dns_resolved": dns_resolved,
|
||||
"current_release": VERSION,
|
||||
@@ -756,6 +762,7 @@ async fn diagnostics(_token: AdminToken, ip_header: IpHeader, conn: DbConn) -> A
|
||||
"db_version": get_sql_server_version(&conn).await,
|
||||
"admin_url": format!("{}/diagnostics", admin_url()),
|
||||
"overrides": &CONFIG.get_overrides().join(", "),
|
||||
"invalid_feature_flags": invalid_feature_flags,
|
||||
"host_arch": env::consts::ARCH,
|
||||
"host_os": env::consts::OS,
|
||||
"tz_env": env::var("TZ").unwrap_or_default(),
|
||||
|
||||
@@ -1328,6 +1328,11 @@ impl<'r> FromRequest<'r> for KnownDevice {
|
||||
|
||||
async fn from_request(req: &'r Request<'_>) -> Outcome<Self, Self::Error> {
|
||||
let email = if let Some(email_b64) = req.headers().get_one("X-Request-Email") {
|
||||
// Bitwarden seems to send padded Base64 strings since 2026.2.1
|
||||
// Since these values are not streamed and Headers are always split by newlines
|
||||
// we can safely ignore padding here and remove any '=' appended.
|
||||
let email_b64 = email_b64.trim_end_matches('=');
|
||||
|
||||
let Ok(email_bytes) = data_encoding::BASE64URL_NOPAD.decode(email_b64.as_bytes()) else {
|
||||
return Outcome::Error((Status::BadRequest, "X-Request-Email value failed to decode as base64url"));
|
||||
};
|
||||
|
||||
@@ -59,7 +59,8 @@ use crate::{
|
||||
error::Error,
|
||||
http_client::make_http_request,
|
||||
mail,
|
||||
util::parse_experimental_client_feature_flags,
|
||||
util::{parse_experimental_client_feature_flags, FeatureFlagFilter},
|
||||
CONFIG,
|
||||
};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
@@ -136,7 +137,7 @@ async fn put_eq_domains(data: Json<EquivDomainData>, headers: Headers, conn: DbC
|
||||
#[get("/hibp/breach?<username>")]
|
||||
async fn hibp_breach(username: &str, _headers: Headers) -> JsonResult {
|
||||
let username: String = url::form_urlencoded::byte_serialize(username.as_bytes()).collect();
|
||||
if let Some(api_key) = crate::CONFIG.hibp_api_key() {
|
||||
if let Some(api_key) = CONFIG.hibp_api_key() {
|
||||
let url = format!(
|
||||
"https://haveibeenpwned.com/api/v3/breachedaccount/{username}?truncateResponse=false&includeUnverified=false"
|
||||
);
|
||||
@@ -197,19 +198,17 @@ fn get_api_webauthn(_headers: Headers) -> Json<Value> {
|
||||
|
||||
#[get("/config")]
|
||||
fn config() -> Json<Value> {
|
||||
let domain = crate::CONFIG.domain();
|
||||
let domain = CONFIG.domain();
|
||||
// Official available feature flags can be found here:
|
||||
// Server (v2025.6.2): https://github.com/bitwarden/server/blob/d094be3267f2030bd0dc62106bc6871cf82682f5/src/Core/Constants.cs#L103
|
||||
// Client (web-v2025.6.1): https://github.com/bitwarden/clients/blob/747c2fd6a1c348a57a76e4a7de8128466ffd3c01/libs/common/src/enums/feature-flag.enum.ts#L12
|
||||
// Android (v2025.6.0): https://github.com/bitwarden/android/blob/b5b022caaad33390c31b3021b2c1205925b0e1a2/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/model/FlagKey.kt#L22
|
||||
// iOS (v2025.6.0): https://github.com/bitwarden/ios/blob/ff06d9c6cc8da89f78f37f376495800201d7261a/BitwardenShared/Core/Platform/Models/Enum/FeatureFlag.swift#L7
|
||||
let mut feature_states =
|
||||
parse_experimental_client_feature_flags(&crate::CONFIG.experimental_client_feature_flags());
|
||||
feature_states.insert("duo-redirect".to_string(), true);
|
||||
feature_states.insert("email-verification".to_string(), true);
|
||||
feature_states.insert("unauth-ui-refresh".to_string(), true);
|
||||
feature_states.insert("enable-pm-flight-recorder".to_string(), true);
|
||||
feature_states.insert("mobile-error-reporting".to_string(), true);
|
||||
// Server (v2026.2.1): https://github.com/bitwarden/server/blob/0e42725d0837bd1c0dabd864ff621a579959744b/src/Core/Constants.cs#L135
|
||||
// Client (v2026.2.1): https://github.com/bitwarden/clients/blob/f96380c3138291a028bdd2c7a5fee540d5c98ba5/libs/common/src/enums/feature-flag.enum.ts#L12
|
||||
// Android (v2026.2.1): https://github.com/bitwarden/android/blob/6902c19c0093fa476bbf74ccaa70c9f14afbb82f/core/src/main/kotlin/com/bitwarden/core/data/manager/model/FlagKey.kt#L31
|
||||
// iOS (v2026.2.1): https://github.com/bitwarden/ios/blob/cdd9ba1770ca2ffc098d02d12cc3208e3a830454/BitwardenShared/Core/Platform/Models/Enum/FeatureFlag.swift#L7
|
||||
let feature_states = parse_experimental_client_feature_flags(
|
||||
&CONFIG.experimental_client_feature_flags(),
|
||||
FeatureFlagFilter::ValidOnly,
|
||||
);
|
||||
// Add default feature_states here if needed, currently no features are needed by default.
|
||||
|
||||
Json(json!({
|
||||
// Note: The clients use this version to handle backwards compatibility concerns
|
||||
@@ -225,7 +224,7 @@ fn config() -> Json<Value> {
|
||||
"url": "https://github.com/dani-garcia/vaultwarden"
|
||||
},
|
||||
"settings": {
|
||||
"disableUserRegistration": crate::CONFIG.is_signup_disabled()
|
||||
"disableUserRegistration": CONFIG.is_signup_disabled()
|
||||
},
|
||||
"environment": {
|
||||
"vault": domain,
|
||||
@@ -278,7 +277,7 @@ async fn accept_org_invite(
|
||||
|
||||
member.save(conn).await?;
|
||||
|
||||
if crate::CONFIG.mail_enabled() {
|
||||
if CONFIG.mail_enabled() {
|
||||
let org = match Organization::find_by_uuid(&member.org_uuid, conn).await {
|
||||
Some(org) => org,
|
||||
None => err!("Organization not found."),
|
||||
|
||||
@@ -395,7 +395,7 @@ async fn get_org_collections_details(org_id: OrganizationId, headers: ManagerHea
|
||||
Membership::find_confirmed_by_org(&org_id, &conn).await.into_iter().map(|m| (m.uuid, m.atype)).collect();
|
||||
|
||||
// check if current user has full access to the organization (either directly or via any group)
|
||||
let has_full_access_to_org = member.access_all
|
||||
let has_full_access_to_org = member.has_full_access()
|
||||
|| (CONFIG.org_groups_enabled() && GroupUser::has_full_access_by_member(&org_id, &member.uuid, &conn).await);
|
||||
|
||||
// Get all admins, owners and managers who can manage/access all
|
||||
@@ -421,6 +421,11 @@ async fn get_org_collections_details(org_id: OrganizationId, headers: ManagerHea
|
||||
|| (CONFIG.org_groups_enabled()
|
||||
&& GroupUser::has_access_to_collection_by_member(&col.uuid, &member.uuid, &conn).await);
|
||||
|
||||
// If the user is a manager, and is not assigned to this collection, skip this and continue with the next collection
|
||||
if !assigned {
|
||||
continue;
|
||||
}
|
||||
|
||||
// get the users assigned directly to the given collection
|
||||
let mut users: Vec<Value> = col_users
|
||||
.iter()
|
||||
|
||||
@@ -438,7 +438,7 @@ pub async fn validate_webauthn_login(user_id: &UserId, response: &str, conn: &Db
|
||||
// We need to check for and update the backup_eligible flag when needed.
|
||||
// Vaultwarden did not have knowledge of this flag prior to migrating to webauthn-rs v0.5.x
|
||||
// Because of this we check the flag at runtime and update the registrations and state when needed
|
||||
check_and_update_backup_eligible(user_id, &rsp, &mut registrations, &mut state, conn).await?;
|
||||
let backup_flags_updated = check_and_update_backup_eligible(&rsp, &mut registrations, &mut state)?;
|
||||
|
||||
let authentication_result = WEBAUTHN.finish_passkey_authentication(&rsp, &state)?;
|
||||
|
||||
@@ -446,7 +446,8 @@ pub async fn validate_webauthn_login(user_id: &UserId, response: &str, conn: &Db
|
||||
if ct_eq(reg.credential.cred_id(), authentication_result.cred_id()) {
|
||||
// If the cred id matches and the credential is updated, Some(true) is returned
|
||||
// In those cases, update the record, else leave it alone
|
||||
if reg.credential.update_credential(&authentication_result) == Some(true) {
|
||||
let credential_updated = reg.credential.update_credential(&authentication_result) == Some(true);
|
||||
if credential_updated || backup_flags_updated {
|
||||
TwoFactor::new(user_id.clone(), TwoFactorType::Webauthn, serde_json::to_string(®istrations)?)
|
||||
.save(conn)
|
||||
.await?;
|
||||
@@ -463,13 +464,11 @@ pub async fn validate_webauthn_login(user_id: &UserId, response: &str, conn: &Db
|
||||
)
|
||||
}
|
||||
|
||||
async fn check_and_update_backup_eligible(
|
||||
user_id: &UserId,
|
||||
fn check_and_update_backup_eligible(
|
||||
rsp: &PublicKeyCredential,
|
||||
registrations: &mut Vec<WebauthnRegistration>,
|
||||
state: &mut PasskeyAuthentication,
|
||||
conn: &DbConn,
|
||||
) -> EmptyResult {
|
||||
) -> Result<bool, Error> {
|
||||
// The feature flags from the response
|
||||
// For details see: https://www.w3.org/TR/webauthn-3/#sctn-authenticator-data
|
||||
const FLAG_BACKUP_ELIGIBLE: u8 = 0b0000_1000;
|
||||
@@ -486,16 +485,7 @@ async fn check_and_update_backup_eligible(
|
||||
let rsp_id = rsp.raw_id.as_slice();
|
||||
for reg in &mut *registrations {
|
||||
if ct_eq(reg.credential.cred_id().as_slice(), rsp_id) {
|
||||
// Try to update the key, and if needed also update the database, before the actual state check is done
|
||||
if reg.set_backup_eligible(backup_eligible, backup_state) {
|
||||
TwoFactor::new(
|
||||
user_id.clone(),
|
||||
TwoFactorType::Webauthn,
|
||||
serde_json::to_string(®istrations)?,
|
||||
)
|
||||
.save(conn)
|
||||
.await?;
|
||||
|
||||
// We also need to adjust the current state which holds the challenge used to start the authentication verification
|
||||
// Because Vaultwarden supports multiple keys, we need to loop through the deserialized state and check which key to update
|
||||
let mut raw_state = serde_json::to_value(&state)?;
|
||||
@@ -517,11 +507,12 @@ async fn check_and_update_backup_eligible(
|
||||
}
|
||||
|
||||
*state = serde_json::from_value(raw_state)?;
|
||||
return Ok(true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
@@ -513,13 +513,11 @@ fn parse_sizes(sizes: &str) -> (u16, u16) {
|
||||
|
||||
if !sizes.is_empty() {
|
||||
match ICON_SIZE_REGEX.captures(sizes.trim()) {
|
||||
None => {}
|
||||
Some(dimensions) => {
|
||||
if dimensions.len() >= 3 {
|
||||
width = dimensions[1].parse::<u16>().unwrap_or_default();
|
||||
height = dimensions[2].parse::<u16>().unwrap_or_default();
|
||||
}
|
||||
Some(dimensions) if dimensions.len() >= 3 => {
|
||||
width = dimensions[1].parse::<u16>().unwrap_or_default();
|
||||
height = dimensions[2].parse::<u16>().unwrap_or_default();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -633,6 +633,19 @@ async fn _user_api_key_login(
|
||||
Value::Null
|
||||
};
|
||||
|
||||
let account_keys = if user.private_key.is_some() {
|
||||
json!({
|
||||
"publicKeyEncryptionKeyPair": {
|
||||
"wrappedPrivateKey": user.private_key,
|
||||
"publicKey": user.public_key,
|
||||
"Object": "publicKeyEncryptionKeyPair"
|
||||
},
|
||||
"Object": "privateKeys"
|
||||
})
|
||||
} else {
|
||||
Value::Null
|
||||
};
|
||||
|
||||
// Note: No refresh_token is returned. The CLI just repeats the
|
||||
// client_credentials login flow when the existing token expires.
|
||||
let result = json!({
|
||||
@@ -647,7 +660,9 @@ async fn _user_api_key_login(
|
||||
"KdfMemory": user.client_kdf_memory,
|
||||
"KdfParallelism": user.client_kdf_parallelism,
|
||||
"ResetMasterPassword": false, // TODO: according to official server seems something like: user.password_hash.is_empty(), but would need testing
|
||||
"ForcePasswordReset": false,
|
||||
"scope": AuthMethod::UserApiKey.scope(),
|
||||
"AccountKeys": account_keys,
|
||||
"UserDecryptionOptions": {
|
||||
"HasMasterPassword": has_master_password,
|
||||
"MasterPasswordUnlock": master_password_unlock,
|
||||
@@ -742,7 +757,6 @@ async fn twofactor_auth(
|
||||
use crate::crypto::ct_eq;
|
||||
|
||||
let selected_data = _selected_data(selected_twofactor);
|
||||
let mut remember = data.two_factor_remember.unwrap_or(0);
|
||||
|
||||
match TwoFactorType::from_i32(selected_id) {
|
||||
Some(TwoFactorType::Authenticator) => {
|
||||
@@ -774,13 +788,23 @@ async fn twofactor_auth(
|
||||
}
|
||||
Some(TwoFactorType::Remember) => {
|
||||
match device.twofactor_remember {
|
||||
Some(ref code) if !CONFIG.disable_2fa_remember() && ct_eq(code, twofactor_code) => {
|
||||
remember = 1; // Make sure we also return the token here, otherwise it will only remember the first time
|
||||
}
|
||||
// When a 2FA Remember token is used, check and validate this JWT token, if it is valid, just continue
|
||||
// If it is invalid we need to trigger the 2FA Login prompt
|
||||
Some(ref token)
|
||||
if !CONFIG.disable_2fa_remember()
|
||||
&& (ct_eq(token, twofactor_code)
|
||||
&& auth::decode_2fa_remember(twofactor_code)
|
||||
.is_ok_and(|t| t.sub == device.uuid && t.user_uuid == user.uuid)) => {}
|
||||
_ => {
|
||||
// Always delete the current twofactor remember token here if it exists
|
||||
if device.twofactor_remember.is_some() {
|
||||
device.delete_twofactor_remember();
|
||||
// We need to save here, since we send a err_json!() which prevents saving `device` at a later stage
|
||||
device.save(true, conn).await?;
|
||||
}
|
||||
err_json!(
|
||||
_json_err_twofactor(&twofactor_ids, &user.uuid, data, client_version, conn).await?,
|
||||
"2FA Remember token not provided"
|
||||
"2FA Remember token not provided or expired"
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -811,10 +835,10 @@ async fn twofactor_auth(
|
||||
|
||||
TwoFactorIncomplete::mark_complete(&user.uuid, &device.uuid, conn).await?;
|
||||
|
||||
let remember = data.two_factor_remember.unwrap_or(0);
|
||||
let two_factor = if !CONFIG.disable_2fa_remember() && remember == 1 {
|
||||
Some(device.refresh_twofactor_remember())
|
||||
} else {
|
||||
device.delete_twofactor_remember();
|
||||
None
|
||||
};
|
||||
Ok(two_factor)
|
||||
|
||||
30
src/auth.rs
30
src/auth.rs
@@ -46,6 +46,7 @@ static JWT_FILE_DOWNLOAD_ISSUER: LazyLock<String> =
|
||||
LazyLock::new(|| format!("{}|file_download", CONFIG.domain_origin()));
|
||||
static JWT_REGISTER_VERIFY_ISSUER: LazyLock<String> =
|
||||
LazyLock::new(|| format!("{}|register_verify", CONFIG.domain_origin()));
|
||||
static JWT_2FA_REMEMBER_ISSUER: LazyLock<String> = LazyLock::new(|| format!("{}|2faremember", CONFIG.domain_origin()));
|
||||
|
||||
static PRIVATE_RSA_KEY: OnceLock<EncodingKey> = OnceLock::new();
|
||||
static PUBLIC_RSA_KEY: OnceLock<DecodingKey> = OnceLock::new();
|
||||
@@ -160,6 +161,10 @@ pub fn decode_register_verify(token: &str) -> Result<RegisterVerifyClaims, Error
|
||||
decode_jwt(token, JWT_REGISTER_VERIFY_ISSUER.to_string())
|
||||
}
|
||||
|
||||
pub fn decode_2fa_remember(token: &str) -> Result<TwoFactorRememberClaims, Error> {
|
||||
decode_jwt(token, JWT_2FA_REMEMBER_ISSUER.to_string())
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct LoginJwtClaims {
|
||||
// Not before
|
||||
@@ -440,6 +445,31 @@ pub fn generate_register_verify_claims(email: String, name: Option<String>, veri
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct TwoFactorRememberClaims {
|
||||
// Not before
|
||||
pub nbf: i64,
|
||||
// Expiration time
|
||||
pub exp: i64,
|
||||
// Issuer
|
||||
pub iss: String,
|
||||
// Subject
|
||||
pub sub: DeviceId,
|
||||
// UserId
|
||||
pub user_uuid: UserId,
|
||||
}
|
||||
|
||||
pub fn generate_2fa_remember_claims(device_uuid: DeviceId, user_uuid: UserId) -> TwoFactorRememberClaims {
|
||||
let time_now = Utc::now();
|
||||
TwoFactorRememberClaims {
|
||||
nbf: time_now.timestamp(),
|
||||
exp: (time_now + TimeDelta::try_days(30).unwrap()).timestamp(),
|
||||
iss: JWT_2FA_REMEMBER_ISSUER.to_string(),
|
||||
sub: device_uuid,
|
||||
user_uuid,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct BasicJwtClaims {
|
||||
// Not before
|
||||
|
||||
@@ -14,7 +14,10 @@ use serde::de::{self, Deserialize, Deserializer, MapAccess, Visitor};
|
||||
|
||||
use crate::{
|
||||
error::Error,
|
||||
util::{get_active_web_release, get_env, get_env_bool, is_valid_email, parse_experimental_client_feature_flags},
|
||||
util::{
|
||||
get_active_web_release, get_env, get_env_bool, is_valid_email, parse_experimental_client_feature_flags,
|
||||
FeatureFlagFilter,
|
||||
},
|
||||
};
|
||||
|
||||
static CONFIG_FILE: LazyLock<String> = LazyLock::new(|| {
|
||||
@@ -920,7 +923,7 @@ make_config! {
|
||||
},
|
||||
}
|
||||
|
||||
fn validate_config(cfg: &ConfigItems) -> Result<(), Error> {
|
||||
fn validate_config(cfg: &ConfigItems, on_update: bool) -> Result<(), Error> {
|
||||
// Validate connection URL is valid and DB feature is enabled
|
||||
#[cfg(sqlite)]
|
||||
{
|
||||
@@ -1026,33 +1029,17 @@ fn validate_config(cfg: &ConfigItems) -> Result<(), Error> {
|
||||
}
|
||||
}
|
||||
|
||||
// Server (v2025.6.2): https://github.com/bitwarden/server/blob/d094be3267f2030bd0dc62106bc6871cf82682f5/src/Core/Constants.cs#L103
|
||||
// Client (web-v2025.6.1): https://github.com/bitwarden/clients/blob/747c2fd6a1c348a57a76e4a7de8128466ffd3c01/libs/common/src/enums/feature-flag.enum.ts#L12
|
||||
// Android (v2025.6.0): https://github.com/bitwarden/android/blob/b5b022caaad33390c31b3021b2c1205925b0e1a2/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/model/FlagKey.kt#L22
|
||||
// iOS (v2025.6.0): https://github.com/bitwarden/ios/blob/ff06d9c6cc8da89f78f37f376495800201d7261a/BitwardenShared/Core/Platform/Models/Enum/FeatureFlag.swift#L7
|
||||
//
|
||||
// NOTE: Move deprecated flags to the utils::parse_experimental_client_feature_flags() DEPRECATED_FLAGS const!
|
||||
const KNOWN_FLAGS: &[&str] = &[
|
||||
// Autofill Team
|
||||
"inline-menu-positioning-improvements",
|
||||
"inline-menu-totp",
|
||||
"ssh-agent",
|
||||
// Key Management Team
|
||||
"ssh-key-vault-item",
|
||||
"pm-25373-windows-biometrics-v2",
|
||||
// Tools
|
||||
"export-attachments",
|
||||
// Mobile Team
|
||||
"anon-addy-self-host-alias",
|
||||
"simple-login-self-host-alias",
|
||||
"mutual-tls",
|
||||
];
|
||||
let configured_flags = parse_experimental_client_feature_flags(&cfg.experimental_client_feature_flags);
|
||||
let invalid_flags: Vec<_> = configured_flags.keys().filter(|flag| !KNOWN_FLAGS.contains(&flag.as_str())).collect();
|
||||
let invalid_flags =
|
||||
parse_experimental_client_feature_flags(&cfg.experimental_client_feature_flags, FeatureFlagFilter::InvalidOnly);
|
||||
if !invalid_flags.is_empty() {
|
||||
err!(format!("Unrecognized experimental client feature flags: {invalid_flags:?}.\n\n\
|
||||
let feature_flags_error = format!("Unrecognized experimental client feature flags: {:?}.\n\
|
||||
Please ensure all feature flags are spelled correctly and that they are supported in this version.\n\
|
||||
Supported flags: {KNOWN_FLAGS:?}"));
|
||||
Supported flags: {:?}\n", invalid_flags, SUPPORTED_FEATURE_FLAGS);
|
||||
if on_update {
|
||||
err!(feature_flags_error);
|
||||
} else {
|
||||
println!("[WARNING] {feature_flags_error}");
|
||||
}
|
||||
}
|
||||
|
||||
const MAX_FILESIZE_KB: i64 = i64::MAX >> 10;
|
||||
@@ -1471,6 +1458,35 @@ pub enum PathType {
|
||||
RsaKey,
|
||||
}
|
||||
|
||||
// Official available feature flags can be found here:
|
||||
// Server (v2026.2.1): https://github.com/bitwarden/server/blob/0e42725d0837bd1c0dabd864ff621a579959744b/src/Core/Constants.cs#L135
|
||||
// Client (v2026.2.1): https://github.com/bitwarden/clients/blob/f96380c3138291a028bdd2c7a5fee540d5c98ba5/libs/common/src/enums/feature-flag.enum.ts#L12
|
||||
// Android (v2026.2.1): https://github.com/bitwarden/android/blob/6902c19c0093fa476bbf74ccaa70c9f14afbb82f/core/src/main/kotlin/com/bitwarden/core/data/manager/model/FlagKey.kt#L31
|
||||
// iOS (v2026.2.1): https://github.com/bitwarden/ios/blob/cdd9ba1770ca2ffc098d02d12cc3208e3a830454/BitwardenShared/Core/Platform/Models/Enum/FeatureFlag.swift#L7
|
||||
pub const SUPPORTED_FEATURE_FLAGS: &[&str] = &[
|
||||
// Architecture
|
||||
"desktop-ui-migration-milestone-1",
|
||||
"desktop-ui-migration-milestone-2",
|
||||
"desktop-ui-migration-milestone-3",
|
||||
"desktop-ui-migration-milestone-4",
|
||||
// Auth Team
|
||||
"pm-5594-safari-account-switching",
|
||||
// Autofill Team
|
||||
"ssh-agent",
|
||||
"ssh-agent-v2",
|
||||
// Key Management Team
|
||||
"ssh-key-vault-item",
|
||||
"pm-25373-windows-biometrics-v2",
|
||||
// Mobile Team
|
||||
"anon-addy-self-host-alias",
|
||||
"simple-login-self-host-alias",
|
||||
"mutual-tls",
|
||||
"cxp-import-mobile",
|
||||
"cxp-export-mobile",
|
||||
// Platform Team
|
||||
"pm-30529-webauthn-related-origins",
|
||||
];
|
||||
|
||||
impl Config {
|
||||
pub async fn load() -> Result<Self, Error> {
|
||||
// Loading from env and file
|
||||
@@ -1484,7 +1500,7 @@ impl Config {
|
||||
// Fill any missing with defaults
|
||||
let config = builder.build();
|
||||
if !SKIP_CONFIG_VALIDATION.load(Ordering::Relaxed) {
|
||||
validate_config(&config)?;
|
||||
validate_config(&config, false)?;
|
||||
}
|
||||
|
||||
Ok(Config {
|
||||
@@ -1520,7 +1536,7 @@ impl Config {
|
||||
let env = &self.inner.read().unwrap()._env;
|
||||
env.merge(&builder, false, &mut overrides).build()
|
||||
};
|
||||
validate_config(&config)?;
|
||||
validate_config(&config, true)?;
|
||||
|
||||
// Save both the user and the combined config
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use chrono::{NaiveDateTime, Utc};
|
||||
|
||||
use data_encoding::{BASE64, BASE64URL};
|
||||
use data_encoding::BASE64URL;
|
||||
use derive_more::{Display, From};
|
||||
use serde_json::Value;
|
||||
|
||||
@@ -67,10 +67,13 @@ impl Device {
|
||||
}
|
||||
|
||||
pub fn refresh_twofactor_remember(&mut self) -> String {
|
||||
let twofactor_remember = crypto::encode_random_bytes::<180>(&BASE64);
|
||||
self.twofactor_remember = Some(twofactor_remember.clone());
|
||||
use crate::auth::{encode_jwt, generate_2fa_remember_claims};
|
||||
|
||||
twofactor_remember
|
||||
let two_factor_remember_claim = generate_2fa_remember_claims(self.uuid.clone(), self.user_uuid.clone());
|
||||
let two_factor_remember_string = encode_jwt(&two_factor_remember_claim);
|
||||
self.twofactor_remember = Some(two_factor_remember_string.clone());
|
||||
|
||||
two_factor_remember_string
|
||||
}
|
||||
|
||||
pub fn delete_twofactor_remember(&mut self) {
|
||||
|
||||
@@ -269,7 +269,7 @@ impl OrgPolicy {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(user) = Membership::find_by_user_and_org(user_uuid, &policy.org_uuid, conn).await {
|
||||
if let Some(user) = Membership::find_confirmed_by_user_and_org(user_uuid, &policy.org_uuid, conn).await {
|
||||
if user.atype < MembershipType::Admin {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use std::{borrow::Cow, sync::LazyLock, time::Duration};
|
||||
|
||||
use mini_moka::sync::Cache;
|
||||
use openidconnect::{core::*, reqwest, *};
|
||||
use regex::Regex;
|
||||
use url::Url;
|
||||
@@ -13,9 +12,14 @@ use crate::{
|
||||
};
|
||||
|
||||
static CLIENT_CACHE_KEY: LazyLock<String> = LazyLock::new(|| "sso-client".to_string());
|
||||
static CLIENT_CACHE: LazyLock<Cache<String, Client>> = LazyLock::new(|| {
|
||||
Cache::builder().max_capacity(1).time_to_live(Duration::from_secs(CONFIG.sso_client_cache_expiration())).build()
|
||||
static CLIENT_CACHE: LazyLock<moka::sync::Cache<String, Client>> = LazyLock::new(|| {
|
||||
moka::sync::Cache::builder()
|
||||
.max_capacity(1)
|
||||
.time_to_live(Duration::from_secs(CONFIG.sso_client_cache_expiration()))
|
||||
.build()
|
||||
});
|
||||
static REFRESH_CACHE: LazyLock<moka::future::Cache<String, Result<RefreshTokenResponse, String>>> =
|
||||
LazyLock::new(|| moka::future::Cache::builder().max_capacity(1000).time_to_live(Duration::from_secs(30)).build());
|
||||
|
||||
/// OpenID Connect Core client.
|
||||
pub type CustomClient = openidconnect::Client<
|
||||
@@ -38,6 +42,8 @@ pub type CustomClient = openidconnect::Client<
|
||||
EndpointSet,
|
||||
>;
|
||||
|
||||
pub type RefreshTokenResponse = (Option<String>, String, Option<Duration>);
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Client {
|
||||
pub http_client: reqwest::Client,
|
||||
@@ -231,23 +237,29 @@ impl Client {
|
||||
verifier
|
||||
}
|
||||
|
||||
pub async fn exchange_refresh_token(
|
||||
refresh_token: String,
|
||||
) -> ApiResult<(Option<String>, String, Option<Duration>)> {
|
||||
pub async fn exchange_refresh_token(refresh_token: String) -> ApiResult<RefreshTokenResponse> {
|
||||
let client = Client::cached().await?;
|
||||
|
||||
REFRESH_CACHE
|
||||
.get_with(refresh_token.clone(), async move { client._exchange_refresh_token(refresh_token).await })
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn _exchange_refresh_token(&self, refresh_token: String) -> Result<RefreshTokenResponse, String> {
|
||||
let rt = RefreshToken::new(refresh_token);
|
||||
|
||||
let client = Client::cached().await?;
|
||||
let token_response =
|
||||
match client.core_client.exchange_refresh_token(&rt).request_async(&client.http_client).await {
|
||||
Err(err) => err!(format!("Request to exchange_refresh_token endpoint failed: {:?}", err)),
|
||||
Ok(token_response) => token_response,
|
||||
};
|
||||
|
||||
Ok((
|
||||
token_response.refresh_token().map(|token| token.secret().clone()),
|
||||
token_response.access_token().secret().clone(),
|
||||
token_response.expires_in(),
|
||||
))
|
||||
match self.core_client.exchange_refresh_token(&rt).request_async(&self.http_client).await {
|
||||
Err(err) => {
|
||||
error!("Request to exchange_refresh_token endpoint failed: {err}");
|
||||
Err(format!("Request to exchange_refresh_token endpoint failed: {err}"))
|
||||
}
|
||||
Ok(token_response) => Ok((
|
||||
token_response.refresh_token().map(|token| token.secret().clone()),
|
||||
token_response.access_token().secret().clone(),
|
||||
token_response.expires_in(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
7
src/static/scripts/admin_diagnostics.js
vendored
7
src/static/scripts/admin_diagnostics.js
vendored
@@ -109,6 +109,9 @@ async function generateSupportString(event, dj) {
|
||||
supportString += "* Websocket Check: disabled\n";
|
||||
}
|
||||
supportString += `* HTTP Response Checks: ${httpResponseCheck}\n`;
|
||||
if (dj.invalid_feature_flags != "") {
|
||||
supportString += `* Invalid feature flags: true\n`;
|
||||
}
|
||||
|
||||
const jsonResponse = await fetch(`${BASE_URL}/admin/diagnostics/config`, {
|
||||
"headers": { "Accept": "application/json" }
|
||||
@@ -128,6 +131,10 @@ async function generateSupportString(event, dj) {
|
||||
supportString += `\n**Environment settings which are overridden:** ${dj.overrides}\n`;
|
||||
}
|
||||
|
||||
if (dj.invalid_feature_flags != "") {
|
||||
supportString += `\n**Invalid feature flags:** ${dj.invalid_feature_flags}\n`;
|
||||
}
|
||||
|
||||
// Add http response check messages if they exists
|
||||
if (httpResponseCheck === false) {
|
||||
supportString += "\n**Failed HTTP Checks:**\n";
|
||||
|
||||
@@ -194,6 +194,14 @@
|
||||
<dd class="col-sm-7">
|
||||
<span id="http-response-errors" class="d-block"></span>
|
||||
</dd>
|
||||
{{#if page_data.invalid_feature_flags}}
|
||||
<dt class="col-sm-5">Invalid Feature Flags
|
||||
<span class="badge bg-warning text-dark abbr-badge" id="feature-flag-warning" title="Some feature flags are invalid or outdated!">Warning</span>
|
||||
</dt>
|
||||
<dd class="col-sm-7">
|
||||
<span id="feature-flags" class="d-block"><b>Flags:</b> <span id="feature-flags-string">{{page_data.invalid_feature_flags}}</span></span>
|
||||
</dd>
|
||||
{{/if}}
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
36
src/util.rs
36
src/util.rs
@@ -16,7 +16,10 @@ use tokio::{
|
||||
time::{sleep, Duration},
|
||||
};
|
||||
|
||||
use crate::{config::PathType, CONFIG};
|
||||
use crate::{
|
||||
config::{PathType, SUPPORTED_FEATURE_FLAGS},
|
||||
CONFIG,
|
||||
};
|
||||
|
||||
pub struct AppHeaders();
|
||||
|
||||
@@ -153,9 +156,11 @@ impl Cors {
|
||||
fn get_allowed_origin(headers: &HeaderMap<'_>) -> Option<String> {
|
||||
let origin = Cors::get_header(headers, "Origin");
|
||||
let safari_extension_origin = "file://";
|
||||
let desktop_custom_file_origin = "bw-desktop-file://bundle";
|
||||
|
||||
if origin == CONFIG.domain_origin()
|
||||
|| origin == safari_extension_origin
|
||||
|| origin == desktop_custom_file_origin
|
||||
|| (CONFIG.sso_enabled() && origin == CONFIG.sso_authority())
|
||||
{
|
||||
Some(origin)
|
||||
@@ -763,21 +768,28 @@ pub fn convert_json_key_lcase_first(src_json: Value) -> Value {
|
||||
}
|
||||
}
|
||||
|
||||
pub enum FeatureFlagFilter {
|
||||
#[allow(dead_code)]
|
||||
Unfiltered,
|
||||
ValidOnly,
|
||||
InvalidOnly,
|
||||
}
|
||||
|
||||
/// Parses the experimental client feature flags string into a HashMap.
|
||||
pub fn parse_experimental_client_feature_flags(experimental_client_feature_flags: &str) -> HashMap<String, bool> {
|
||||
// These flags could still be configured, but are deprecated and not used anymore
|
||||
// To prevent old installations from starting filter these out and not error out
|
||||
const DEPRECATED_FLAGS: &[&str] =
|
||||
&["autofill-overlay", "autofill-v2", "browser-fileless-import", "extension-refresh", "fido2-vault-credentials"];
|
||||
pub fn parse_experimental_client_feature_flags(
|
||||
experimental_client_feature_flags: &str,
|
||||
filter_mode: FeatureFlagFilter,
|
||||
) -> HashMap<String, bool> {
|
||||
experimental_client_feature_flags
|
||||
.split(',')
|
||||
.filter_map(|f| {
|
||||
let flag = f.trim();
|
||||
if !flag.is_empty() && !DEPRECATED_FLAGS.contains(&flag) {
|
||||
return Some((flag.to_owned(), true));
|
||||
}
|
||||
None
|
||||
.map(str::trim)
|
||||
.filter(|flag| !flag.is_empty())
|
||||
.filter(|flag| match filter_mode {
|
||||
FeatureFlagFilter::Unfiltered => true,
|
||||
FeatureFlagFilter::ValidOnly => SUPPORTED_FEATURE_FLAGS.contains(flag),
|
||||
FeatureFlagFilter::InvalidOnly => !SUPPORTED_FEATURE_FLAGS.contains(flag),
|
||||
})
|
||||
.map(|flag| (flag.to_owned(), true))
|
||||
.collect()
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user