Compare commits

...

26 Commits

Author SHA1 Message Date
Helmut K. C. Tessarek ad8484a2d5 feat: add ip address in logs when email 2fa token is invalid or not available (#5779)
* Update email.rs

Add ip_src on logs when email 2fa token is invalid or not available
Changes for fail2ban purposes

* Update email.rs

removed current_time

* fix: compile error

---------

Co-authored-by: setsecurity <set.ghost@gmail.com>
2025-05-12 19:27:43 +02:00
Josh 9813e480c0 Fix minimum Android version for self-host email alias feature flags (#5802)
* Fix minimum Android version for self-host email alias feature flags

* Update documentation for iOS
2025-05-12 19:25:46 +02:00
Timshel bfe172702a Fix Yubico toggle (#5833) 2025-05-12 19:24:27 +02:00
Timshel df42b6d6b0 Fix Yubico toggle (#5833) 2025-05-12 19:24:12 +02:00
Helmut K. C. Tessarek 2697fe8aba feat: add feature flag export-attachments (#5784) 2025-05-01 17:40:26 +02:00
Stefan Melmuk 674e444d67 respond with cipher json when deleting attachments (#5823) 2025-05-01 17:28:23 +02:00
Timshel 0d16da440d On member invite and edit access_all is not sent anymore (#5673)
* On member invite and edit access_all is not sent anymore

* Use MembershipType ordering for access_all check

Fixes #5711
2025-04-16 17:52:26 +02:00
Mathijs van Veluw 66cf179bca Updates and general fixes (#5762)
Updated all the crates to the latest version.
We can unpin mimalloc, since the musl issues have been fixed
Also fix a RUSTSEC https://osv.dev/vulnerability/RUSTSEC-2025-0023 for tokio

Fixed some clippy lints reported by nightly.

Ensure lints and are also run on the macro crate.
This resulted in some lints being triggered, which I fixed.

Updated some GHA uses.

Signed-off-by: BlackDex <black.dex@gmail.com>
2025-04-09 21:21:10 +02:00
Mathijs van Veluw 025bb90f8f Fix debian docker building (#5752)
In previous attempts to get mysqlclient-sys to build and work I added some extra build variables.
These are not needed if you configure pkg-config correctly.
The same goes for OpenSSL btw.

This PR configures the pkg-config in the right way and allows the crates to build using the right lib paths automatically.
Because of this change also the lib/include paths were not needed anymore for some architectures, except for i386.

Also updated crates again.

Signed-off-by: BlackDex <black.dex@gmail.com>
2025-04-05 17:58:32 +02:00
Mathijs van Veluw d5039d9c17 Add Docker Templates pre-commit check (#5749)
Added the same check as done via GitHub Actions to check template changes to the pre-commit checks.
This should catch these mistakes before they are commited and pushed.

Signed-off-by: BlackDex <black.dex@gmail.com>
2025-04-04 19:02:19 +02:00
Daniel García e7c796a660 Verify templates in CI (#5748)
* Verify templates in CI

* No need to install packages

* Remove unnecessary fetch depth
2025-04-04 18:14:19 +02:00
Daniel bbbd2f6d15 Update Rust to 1.86.0 (#5744)
- also raise MSRV to 1.84.0

- fix `Dockerfile` template
- remove no longed needed `-vvv` argument for `cargo build`
2025-04-04 18:04:36 +02:00
Mathijs van Veluw a2d7895586 Really fix building (#5745)
Signed-off-by: BlackDex <black.dex@gmail.com>
2025-04-04 16:53:09 +02:00
Mathijs van Veluw 8a0cb1137e Fix mysqlclient-sys building (#5743)
Because of some issues with mysqlclient we need to use buildtime bindgen.
This also needed some extra environment variables to point the bindgen to the correct files and correct version.

Also update some other crates.

Signed-off-by: BlackDex <black.dex@gmail.com>
2025-04-04 16:37:57 +02:00
Timshel f960bf59bb Fix invited user registration without SMTP (#5712) 2025-04-04 13:54:28 +02:00
Mathijs van Veluw 3a1f1bae00 Update deps and web-vault (#5742)
- Updated crates
  Pinned mimalloc, since it has issues with musl
- Updated web-vault to v2025.3.1
- Updated bootstrap

Signed-off-by: BlackDex <black.dex@gmail.com>
2025-04-04 12:18:09 +02:00
Mathijs van Veluw 8dfe805954 Update Rust, Crates and other deps (#5709)
- Updated Rust to v1.85.1
- Updated crates and fixed breaking changes
- Updated datatables js
- Updated GitHub Actions

Signed-off-by: BlackDex <black.dex@gmail.com>
2025-03-19 17:39:53 +01:00
Mathijs van Veluw 07b869b3ef Some fixes for the new web-vault and updates (#5703)
- Added a new org policy
- Some new lint fixes
- Crate updates
  Switched to `pastey`, since `paste` is unmaintained.

Signed-off-by: BlackDex <black.dex@gmail.com>
2025-03-17 23:02:02 +01:00
Daniel García 2a18665288 Implement new registration flow with email verification (#5215)
* Implement registration with required verified email

* Optional name, emergency access, and signups_allowed

* Implement org invite, remove unneeded invite accept

* fix invitation logic for new registration flow (#5691)

* fix invitation logic for new registration flow

* clarify email_2fa_enforce_on_verified_invite

---------

Co-authored-by: Stefan Melmuk <509385+stefan0xC@users.noreply.github.com>
2025-03-17 16:28:01 +01:00
Josh 71952a4ab5 Add AnonAddy/SimpleLogin self host feature flag (#5694)
Co-authored-by: Daniel García <dani-garcia@users.noreply.github.com>
2025-03-15 19:57:04 +01:00
Ben Sherman 994d157064 Add support for mutual-tls feature flag (#5698)
* Add support for mutual-tls feature flag

* Fix formatting

---------

Co-authored-by: Daniel García <dani-garcia@users.noreply.github.com>
2025-03-15 19:46:42 +01:00
Timshel 1dae6093c9 Use subtle to replace deprecated ring::constant_time::verify_slices_are_equal (#5680) 2025-03-15 19:33:17 +01:00
Daniel 6edceb5f7a Update Rust to 1.85.0 (#5634)
- also update the crates
2025-02-24 12:12:34 +01:00
Stefan Melmuk 359a4a088a allow CLI to upload files with truncated filenames (#5618)
due to a bug in the CLI the filename in the form-data is not complete if
the encrypted filename happens to contain a /
2025-02-19 10:40:59 +01:00
Mathijs van Veluw 3baffeee9a Fix db issues with Option<> values and upd crates (#5594)
Some tables were lacking an option to convert Option<> to NULL.
This commit will fix that.

Also updated the crates to the latest version available.
2025-02-14 17:58:57 +01:00
Daniel d5c353427d Update crates & fix CVE-2025-25188 (#5576) 2025-02-12 10:21:12 +01:00
49 changed files with 1635 additions and 845 deletions
+7 -2
View File
@@ -229,7 +229,8 @@
# SIGNUPS_ALLOWED=true # SIGNUPS_ALLOWED=true
## Controls if new users need to verify their email address upon registration ## Controls if new users need to verify their email address upon registration
## Note that setting this option to true prevents logins until the email address has been verified! ## On new client versions, this will require the user to verify their email at signup time.
## On older clients, it will require the user to verify their email before they can log in.
## The welcome email will include a verification link, and login attempts will periodically ## The welcome email will include a verification link, and login attempts will periodically
## trigger another verification email to be sent. ## trigger another verification email to be sent.
# SIGNUPS_VERIFY=false # SIGNUPS_VERIFY=false
@@ -353,6 +354,10 @@
## - "inline-menu-positioning-improvements": Enable the use of inline menu password generator and identity suggestions in the browser extension. ## - "inline-menu-positioning-improvements": Enable the use of inline menu password generator and identity suggestions in the browser extension.
## - "ssh-key-vault-item": Enable the creation and use of SSH key vault items. (Needs clients >=2024.12.0) ## - "ssh-key-vault-item": Enable the creation and use of SSH key vault items. (Needs clients >=2024.12.0)
## - "ssh-agent": Enable SSH agent support on Desktop. (Needs desktop >=2024.12.0) ## - "ssh-agent": Enable SSH agent support on Desktop. (Needs desktop >=2024.12.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)
## - "export-attachments": Enable support for exporting attachments (Clients >=2025.4.0)
# EXPERIMENTAL_CLIENT_FEATURE_FLAGS=fido2-vault-credentials # EXPERIMENTAL_CLIENT_FEATURE_FLAGS=fido2-vault-credentials
## Require new device emails. When a user logs in an email is required to be sent. ## Require new device emails. When a user logs in an email is required to be sent.
@@ -486,7 +491,7 @@
## Maximum attempts before an email token is reset and a new email will need to be sent. ## Maximum attempts before an email token is reset and a new email will need to be sent.
# EMAIL_ATTEMPTS_LIMIT=3 # EMAIL_ATTEMPTS_LIMIT=3
## ##
## Setup email 2FA regardless of any organization policy ## Setup email 2FA on registration regardless of any organization policy
# EMAIL_2FA_ENFORCE_ON_VERIFIED_INVITE=false # EMAIL_2FA_ENFORCE_ON_VERIFIED_INVITE=false
## Automatically setup email 2FA as fallback provider when needed ## Automatically setup email 2FA as fallback provider when needed
# EMAIL_2FA_AUTO_FALLBACK=false # EMAIL_2FA_AUTO_FALLBACK=false
+2
View File
@@ -1,3 +1,5 @@
/.github @dani-garcia @BlackDex /.github @dani-garcia @BlackDex
/.github/** @dani-garcia @BlackDex
/.github/CODEOWNERS @dani-garcia @BlackDex /.github/CODEOWNERS @dani-garcia @BlackDex
/.github/workflows/** @dani-garcia @BlackDex /.github/workflows/** @dani-garcia @BlackDex
/SECURITY.md @dani-garcia @BlackDex
+3 -3
View File
@@ -80,7 +80,7 @@ jobs:
# Only install the clippy and rustfmt components on the default rust-toolchain # Only install the clippy and rustfmt components on the default rust-toolchain
- name: "Install rust-toolchain version" - name: "Install rust-toolchain version"
uses: dtolnay/rust-toolchain@c5a29ddb4d9d194e7c84ec8c3fba61b1c31fee8c # master @ Jan 30, 2025, 8:16 PM GMT+1 uses: dtolnay/rust-toolchain@56f84321dbccf38fb67ce29ab63e4754056677e0 # master @ Mar 18, 2025, 8:14 PM GMT+1
if: ${{ matrix.channel == 'rust-toolchain' }} if: ${{ matrix.channel == 'rust-toolchain' }}
with: with:
toolchain: "${{steps.toolchain.outputs.RUST_TOOLCHAIN}}" toolchain: "${{steps.toolchain.outputs.RUST_TOOLCHAIN}}"
@@ -90,7 +90,7 @@ jobs:
# Install the any other channel to be used for which we do not execute clippy and rustfmt # Install the any other channel to be used for which we do not execute clippy and rustfmt
- name: "Install MSRV version" - name: "Install MSRV version"
uses: dtolnay/rust-toolchain@c5a29ddb4d9d194e7c84ec8c3fba61b1c31fee8c # master @ Jan 30, 2025, 8:16 PM GMT+1 uses: dtolnay/rust-toolchain@56f84321dbccf38fb67ce29ab63e4754056677e0 # master @ Mar 18, 2025, 8:14 PM GMT+1
if: ${{ matrix.channel != 'rust-toolchain' }} if: ${{ matrix.channel != 'rust-toolchain' }}
with: with:
toolchain: "${{steps.toolchain.outputs.RUST_TOOLCHAIN}}" toolchain: "${{steps.toolchain.outputs.RUST_TOOLCHAIN}}"
@@ -115,7 +115,7 @@ jobs:
# Enable Rust Caching # Enable Rust Caching
- name: Rust Caching - name: Rust Caching
uses: Swatinem/rust-cache@f0deed1e0edfc6a9be95417288c0e1099b1eeec3 # v2.7.7 uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
with: with:
# Use a custom prefix-key to force a fresh start. This is sometimes needed with bigger changes. # 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. # Like changing the build host from Ubuntu 20.04 to 22.04 for example.
+28
View File
@@ -0,0 +1,28 @@
name: Check templates
permissions: {}
on: [ push, pull_request ]
jobs:
docker-templates:
permissions:
contents: read
runs-on: ubuntu-24.04
timeout-minutes: 30
steps:
# Checkout the repo
- name: "Checkout"
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
with:
persist-credentials: false
# End Checkout the repo
- name: Run make to rebuild templates
working-directory: docker
run: make
- name: Check for unstaged changes
working-directory: docker
run: git diff --exit-code
continue-on-error: false
+1 -1
View File
@@ -14,7 +14,7 @@ jobs:
steps: steps:
# Start Docker Buildx # Start Docker Buildx
- name: Setup Docker Buildx - name: Setup Docker Buildx
uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0 uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
# https://github.com/moby/buildkit/issues/3969 # https://github.com/moby/buildkit/issues/3969
# Also set max parallelism to 2, the default of 4 breaks GitHub Actions and causes OOMKills # Also set max parallelism to 2, the default of 4 breaks GitHub Actions and causes OOMKills
with: with:
+14 -14
View File
@@ -70,13 +70,13 @@ jobs:
steps: steps:
- name: Initialize QEMU binfmt support - name: Initialize QEMU binfmt support
uses: docker/setup-qemu-action@53851d14592bedcffcf25ea515637cff71ef929a # v3.3.0 uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0
with: with:
platforms: "arm64,arm" platforms: "arm64,arm"
# Start Docker Buildx # Start Docker Buildx
- name: Setup Docker Buildx - name: Setup Docker Buildx
uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0 uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
# https://github.com/moby/buildkit/issues/3969 # https://github.com/moby/buildkit/issues/3969
# Also set max parallelism to 2, the default of 4 breaks GitHub Actions and causes OOMKills # Also set max parallelism to 2, the default of 4 breaks GitHub Actions and causes OOMKills
with: with:
@@ -120,7 +120,7 @@ jobs:
# Login to Docker Hub # Login to Docker Hub
- name: Login to Docker Hub - name: Login to Docker Hub
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
with: with:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
@@ -136,7 +136,7 @@ jobs:
# Login to GitHub Container Registry # Login to GitHub Container Registry
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.repository_owner }} username: ${{ github.repository_owner }}
@@ -153,7 +153,7 @@ jobs:
# Login to Quay.io # Login to Quay.io
- name: Login to Quay.io - name: Login to Quay.io
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
with: with:
registry: quay.io registry: quay.io
username: ${{ secrets.QUAY_USERNAME }} username: ${{ secrets.QUAY_USERNAME }}
@@ -192,7 +192,7 @@ jobs:
- name: Bake ${{ matrix.base_image }} containers - name: Bake ${{ matrix.base_image }} containers
id: bake_vw id: bake_vw
uses: docker/bake-action@7bff531c65a5cda33e52e43950a795b91d450f63 # v6.3.0 uses: docker/bake-action@4ba453fbc2db7735392b93edf935aaf9b1e8f747 # v6.5.0
env: env:
BASE_TAGS: "${{ env.BASE_TAGS }}" BASE_TAGS: "${{ env.BASE_TAGS }}"
SOURCE_COMMIT: "${{ env.SOURCE_COMMIT }}" SOURCE_COMMIT: "${{ env.SOURCE_COMMIT }}"
@@ -220,7 +220,7 @@ jobs:
# Attest container images # Attest container images
- name: Attest - docker.io - ${{ matrix.base_image }} - name: Attest - docker.io - ${{ matrix.base_image }}
if: ${{ env.HAVE_DOCKERHUB_LOGIN == 'true' && steps.bake_vw.outputs.metadata != ''}} if: ${{ env.HAVE_DOCKERHUB_LOGIN == 'true' && steps.bake_vw.outputs.metadata != ''}}
uses: actions/attest-build-provenance@520d128f165991a6c774bcb264f323e3d70747f4 # v2.2.0 uses: actions/attest-build-provenance@c074443f1aee8d4aeeae555aebba3282517141b2 # v2.2.3
with: with:
subject-name: ${{ vars.DOCKERHUB_REPO }} subject-name: ${{ vars.DOCKERHUB_REPO }}
subject-digest: ${{ env.DIGEST_SHA }} subject-digest: ${{ env.DIGEST_SHA }}
@@ -228,7 +228,7 @@ jobs:
- name: Attest - ghcr.io - ${{ matrix.base_image }} - name: Attest - ghcr.io - ${{ matrix.base_image }}
if: ${{ env.HAVE_GHCR_LOGIN == 'true' && steps.bake_vw.outputs.metadata != ''}} if: ${{ env.HAVE_GHCR_LOGIN == 'true' && steps.bake_vw.outputs.metadata != ''}}
uses: actions/attest-build-provenance@520d128f165991a6c774bcb264f323e3d70747f4 # v2.2.0 uses: actions/attest-build-provenance@c074443f1aee8d4aeeae555aebba3282517141b2 # v2.2.3
with: with:
subject-name: ${{ vars.GHCR_REPO }} subject-name: ${{ vars.GHCR_REPO }}
subject-digest: ${{ env.DIGEST_SHA }} subject-digest: ${{ env.DIGEST_SHA }}
@@ -236,7 +236,7 @@ jobs:
- name: Attest - quay.io - ${{ matrix.base_image }} - name: Attest - quay.io - ${{ matrix.base_image }}
if: ${{ env.HAVE_QUAY_LOGIN == 'true' && steps.bake_vw.outputs.metadata != ''}} if: ${{ env.HAVE_QUAY_LOGIN == 'true' && steps.bake_vw.outputs.metadata != ''}}
uses: actions/attest-build-provenance@520d128f165991a6c774bcb264f323e3d70747f4 # v2.2.0 uses: actions/attest-build-provenance@c074443f1aee8d4aeeae555aebba3282517141b2 # v2.2.3
with: with:
subject-name: ${{ vars.QUAY_REPO }} subject-name: ${{ vars.QUAY_REPO }}
subject-digest: ${{ env.DIGEST_SHA }} subject-digest: ${{ env.DIGEST_SHA }}
@@ -290,31 +290,31 @@ jobs:
# Upload artifacts to Github Actions and Attest the binaries # Upload artifacts to Github Actions and Attest the binaries
- name: "Upload amd64 artifact ${{ matrix.base_image }}" - name: "Upload amd64 artifact ${{ matrix.base_image }}"
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 #v4.6.0 uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with: with:
name: vaultwarden-${{ env.SOURCE_VERSION }}-linux-amd64-${{ matrix.base_image }} name: vaultwarden-${{ env.SOURCE_VERSION }}-linux-amd64-${{ matrix.base_image }}
path: vaultwarden-amd64-${{ matrix.base_image }} path: vaultwarden-amd64-${{ matrix.base_image }}
- name: "Upload arm64 artifact ${{ matrix.base_image }}" - name: "Upload arm64 artifact ${{ matrix.base_image }}"
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 #v4.6.0 uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with: with:
name: vaultwarden-${{ env.SOURCE_VERSION }}-linux-arm64-${{ matrix.base_image }} name: vaultwarden-${{ env.SOURCE_VERSION }}-linux-arm64-${{ matrix.base_image }}
path: vaultwarden-arm64-${{ matrix.base_image }} path: vaultwarden-arm64-${{ matrix.base_image }}
- name: "Upload armv7 artifact ${{ matrix.base_image }}" - name: "Upload armv7 artifact ${{ matrix.base_image }}"
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 #v4.6.0 uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with: with:
name: vaultwarden-${{ env.SOURCE_VERSION }}-linux-armv7-${{ matrix.base_image }} name: vaultwarden-${{ env.SOURCE_VERSION }}-linux-armv7-${{ matrix.base_image }}
path: vaultwarden-armv7-${{ matrix.base_image }} path: vaultwarden-armv7-${{ matrix.base_image }}
- name: "Upload armv6 artifact ${{ matrix.base_image }}" - name: "Upload armv6 artifact ${{ matrix.base_image }}"
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 #v4.6.0 uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with: with:
name: vaultwarden-${{ env.SOURCE_VERSION }}-linux-armv6-${{ matrix.base_image }} name: vaultwarden-${{ env.SOURCE_VERSION }}-linux-armv6-${{ matrix.base_image }}
path: vaultwarden-armv6-${{ matrix.base_image }} path: vaultwarden-armv6-${{ matrix.base_image }}
- name: "Attest artifacts ${{ matrix.base_image }}" - name: "Attest artifacts ${{ matrix.base_image }}"
uses: actions/attest-build-provenance@520d128f165991a6c774bcb264f323e3d70747f4 # v2.2.0 uses: actions/attest-build-provenance@c074443f1aee8d4aeeae555aebba3282517141b2 # v2.2.3
with: with:
subject-path: vaultwarden-* subject-path: vaultwarden-*
# End Upload artifacts to Github Actions # End Upload artifacts to Github Actions
+1 -1
View File
@@ -36,7 +36,7 @@ jobs:
persist-credentials: false persist-credentials: false
- name: Run Trivy vulnerability scanner - name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@18f2510ee396bbf400402947b394f2dd8c87dbb0 # v0.29.0 uses: aquasecurity/trivy-action@6c175e9c4083a92bbca2f9724c8a5e33bc2d97a5 # v0.30.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
+11 -3
View File
@@ -1,7 +1,7 @@
--- ---
repos: repos:
- repo: https://github.com/pre-commit/pre-commit-hooks - repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.6.0 rev: v5.0.0
hooks: hooks:
- id: check-yaml - id: check-yaml
- id: check-json - id: check-json
@@ -31,7 +31,7 @@ repos:
language: system language: system
args: ["--features", "sqlite,mysql,postgresql,enable_mimalloc", "--"] args: ["--features", "sqlite,mysql,postgresql,enable_mimalloc", "--"]
types_or: [rust, file] types_or: [rust, file]
files: (Cargo.toml|Cargo.lock|rust-toolchain|.*\.rs$) files: (Cargo.toml|Cargo.lock|rust-toolchain.toml|rustfmt.toml|.*\.rs$)
pass_filenames: false pass_filenames: false
- id: cargo-clippy - id: cargo-clippy
name: cargo clippy name: cargo clippy
@@ -40,5 +40,13 @@ repos:
language: system language: system
args: ["--features", "sqlite,mysql,postgresql,enable_mimalloc", "--", "-D", "warnings"] args: ["--features", "sqlite,mysql,postgresql,enable_mimalloc", "--", "-D", "warnings"]
types_or: [rust, file] types_or: [rust, file]
files: (Cargo.toml|Cargo.lock|rust-toolchain|clippy.toml|.*\.rs$) files: (Cargo.toml|Cargo.lock|rust-toolchain.toml|rustfmt.toml|.*\.rs$)
pass_filenames: false pass_filenames: false
- id: check-docker-templates
name: check docker templates
description: Check if the Docker templates are updated
language: system
entry: sh
args:
- "-c"
- "cd docker && make"
Generated
+634 -358
View File
File diff suppressed because it is too large Load Diff
+41 -32
View File
@@ -1,11 +1,12 @@
workspace = { members = ["macros"] } [workspace]
members = ["macros"]
[package] [package]
name = "vaultwarden" name = "vaultwarden"
version = "1.0.0" version = "1.0.0"
authors = ["Daniel García <dani-garcia@users.noreply.github.com>"] authors = ["Daniel García <dani-garcia@users.noreply.github.com>"]
edition = "2021" edition = "2021"
rust-version = "1.83.0" rust-version = "1.84.0"
resolver = "2" resolver = "2"
repository = "https://github.com/dani-garcia/vaultwarden" repository = "https://github.com/dani-garcia/vaultwarden"
@@ -44,7 +45,7 @@ syslog = "7.0.0"
macros = { path = "./macros" } macros = { path = "./macros" }
# Logging # Logging
log = "0.4.25" log = "0.4.27"
fern = { version = "0.7.1", features = ["syslog-7", "reopen-1"] } fern = { version = "0.7.1", features = ["syslog-7", "reopen-1"] }
tracing = { version = "0.1.41", features = ["log"] } # Needed to have lettre and webauthn-rs trace logging to work tracing = { version = "0.1.41", features = ["log"] } # Needed to have lettre and webauthn-rs trace logging to work
@@ -52,12 +53,12 @@ tracing = { version = "0.1.41", features = ["log"] } # Needed to have lettre and
dotenvy = { version = "0.15.7", default-features = false } dotenvy = { version = "0.15.7", default-features = false }
# Lazy initialization # Lazy initialization
once_cell = "1.20.2" once_cell = "1.21.3"
# Numerical libraries # Numerical libraries
num-traits = "0.2.19" num-traits = "0.2.19"
num-derive = "0.4.2" num-derive = "0.4.2"
bigdecimal = "0.4.7" bigdecimal = "0.4.8"
# Web framework # Web framework
rocket = { version = "0.5.1", features = ["tls", "json"], default-features = false } rocket = { version = "0.5.1", features = ["tls", "json"], default-features = false }
@@ -71,43 +72,44 @@ dashmap = "6.1.0"
# Async futures # Async futures
futures = "0.3.31" futures = "0.3.31"
tokio = { version = "1.43.0", features = ["rt-multi-thread", "fs", "io-util", "parking_lot", "time", "signal", "net"] } tokio = { version = "1.44.2", features = ["rt-multi-thread", "fs", "io-util", "parking_lot", "time", "signal", "net"] }
# A generic serialization/deserialization framework # A generic serialization/deserialization framework
serde = { version = "1.0.217", features = ["derive"] } serde = { version = "1.0.219", features = ["derive"] }
serde_json = "1.0.138" serde_json = "1.0.140"
# A safe, extensible ORM and Query builder # A safe, extensible ORM and Query builder
diesel = { version = "2.2.7", features = ["chrono", "r2d2", "numeric"] } diesel = { version = "2.2.9", features = ["chrono", "r2d2", "numeric"] }
diesel_migrations = "2.2.0" diesel_migrations = "2.2.0"
diesel_logger = { version = "0.4.0", optional = true } diesel_logger = { version = "0.4.0", optional = true }
derive_more = { version = "2.0.0", features = ["from", "into", "as_ref", "deref", "display"] } derive_more = { version = "2.0.1", features = ["from", "into", "as_ref", "deref", "display"] }
diesel-derive-newtype = "2.1.2" diesel-derive-newtype = "2.1.2"
# Bundled/Static SQLite # Bundled/Static SQLite
libsqlite3-sys = { version = "0.31.0", features = ["bundled"], optional = true } libsqlite3-sys = { version = "0.32.0", features = ["bundled"], optional = true }
# Crypto-related libraries # Crypto-related libraries
rand = "0.9.0" rand = "0.9.0"
ring = "0.17.8" ring = "0.17.14"
subtle = "2.6.1"
# UUID generation # UUID generation
uuid = { version = "1.12.1", features = ["v4"] } uuid = { version = "1.16.0", features = ["v4"] }
# Date and time libraries # Date and time libraries
chrono = { version = "0.4.39", features = ["clock", "serde"], default-features = false } chrono = { version = "0.4.40", features = ["clock", "serde"], default-features = false }
chrono-tz = "0.10.1" chrono-tz = "0.10.3"
time = "0.3.37" time = "0.3.41"
# Job scheduler # Job scheduler
job_scheduler_ng = "2.0.5" job_scheduler_ng = "2.0.5"
# Data encoding library Hex/Base32/Base64 # Data encoding library Hex/Base32/Base64
data-encoding = "2.7.0" data-encoding = "2.8.0"
# JWT library # JWT library
jsonwebtoken = "9.3.0" jsonwebtoken = "9.3.1"
# TOTP library # TOTP library
totp-lite = "2.0.1" totp-lite = "2.0.1"
@@ -122,47 +124,48 @@ webauthn-rs = "0.3.2"
url = "2.5.4" url = "2.5.4"
# Email libraries # Email libraries
lettre = { version = "0.11.12", features = ["smtp-transport", "sendmail-transport", "builder", "serde", "tokio1-native-tls", "hostname", "tracing", "tokio1"], default-features = false } lettre = { version = "0.11.15", features = ["smtp-transport", "sendmail-transport", "builder", "serde", "tokio1-native-tls", "hostname", "tracing", "tokio1"], default-features = false }
percent-encoding = "2.3.1" # URL encoding library used for URL's in the emails percent-encoding = "2.3.1" # URL encoding library used for URL's in the emails
email_address = "0.2.9" email_address = "0.2.9"
# HTML Template library # HTML Template library
handlebars = { version = "6.3.0", features = ["dir_source"] } handlebars = { version = "6.3.2", 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.12", features = ["native-tls-alpn", "stream", "json", "gzip", "brotli", "socks", "cookies"] } reqwest = { version = "0.12.15", features = ["native-tls-alpn", "stream", "json", "gzip", "brotli", "socks", "cookies"] }
hickory-resolver = "0.24.2" hickory-resolver = "0.25.1"
# Favicon extraction libraries # Favicon extraction libraries
html5gum = "0.7.0" html5gum = "0.7.0"
regex = { version = "1.11.1", features = ["std", "perf", "unicode-perl"], default-features = false } regex = { version = "1.11.1", features = ["std", "perf", "unicode-perl"], default-features = false }
data-url = "0.3.1" data-url = "0.3.1"
bytes = "1.10.0" bytes = "1.10.1"
# Cache function results (Used for version check and favicon fetching) # Cache function results (Used for version check and favicon fetching)
cached = { version = "0.54.0", features = ["async"] } cached = { version = "0.55.1", features = ["async"] }
# Used for custom short lived cookie jar during favicon extraction # Used for custom short lived cookie jar during favicon extraction
cookie = "0.18.1" cookie = "0.18.1"
cookie_store = "0.21.1" cookie_store = "0.21.1"
# Used by U2F, JWT and PostgreSQL # Used by U2F, JWT and PostgreSQL
openssl = "0.10.70" openssl = "0.10.72"
# CLI argument parsing # CLI argument parsing
pico-args = "0.5.0" pico-args = "0.5.0"
# Macro ident concatenation # Macro ident concatenation
paste = "1.0.15" pastey = "0.1.0"
governor = "0.8.0" governor = "0.10.0"
# Check client versions for specific features. # Check client versions for specific features.
semver = "1.0.25" semver = "1.0.26"
# 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.43", features = ["secure"], default-features = false, optional = true } mimalloc = { version = "0.1.46", features = ["secure"], default-features = false, optional = true }
which = "7.0.1"
which = "7.0.3"
# Argon2 library with support for the PHC format # Argon2 library with support for the PHC format
argon2 = "0.5.3" argon2 = "0.5.3"
@@ -213,7 +216,7 @@ codegen-units = 16
# Linting config # Linting config
# https://doc.rust-lang.org/rustc/lints/groups.html # https://doc.rust-lang.org/rustc/lints/groups.html
[lints.rust] [workspace.lints.rust]
# Forbid # Forbid
unsafe_code = "forbid" unsafe_code = "forbid"
non_ascii_idents = "forbid" non_ascii_idents = "forbid"
@@ -243,11 +246,14 @@ if_let_rescope = "allow"
tail_expr_drop_order = "allow" tail_expr_drop_order = "allow"
# https://rust-lang.github.io/rust-clippy/stable/index.html # https://rust-lang.github.io/rust-clippy/stable/index.html
[lints.clippy] [workspace.lints.clippy]
# Warn # Warn
dbg_macro = "warn" dbg_macro = "warn"
todo = "warn" todo = "warn"
# Ignore/Allow
result_large_err = "allow"
# Deny # Deny
case_sensitive_file_extension_comparisons = "deny" case_sensitive_file_extension_comparisons = "deny"
cast_lossless = "deny" cast_lossless = "deny"
@@ -278,3 +284,6 @@ unused_async = "deny"
unused_self = "deny" unused_self = "deny"
verbose_file_reads = "deny" verbose_file_reads = "deny"
zero_sized_map_values = "deny" zero_sized_map_values = "deny"
[lints]
workspace = true
+2 -2
View File
@@ -48,8 +48,8 @@ fn main() {
fn run(args: &[&str]) -> Result<String, std::io::Error> { fn run(args: &[&str]) -> Result<String, std::io::Error> {
let out = Command::new(args[0]).args(&args[1..]).output()?; let out = Command::new(args[0]).args(&args[1..]).output()?;
if !out.status.success() { if !out.status.success() {
use std::io::{Error, ErrorKind}; use std::io::Error;
return Err(Error::new(ErrorKind::Other, "Command not successful")); return Err(Error::other("Command not successful"));
} }
Ok(String::from_utf8(out.stdout).unwrap().trim().to_string()) Ok(String::from_utf8(out.stdout).unwrap().trim().to_string())
} }
+3 -3
View File
@@ -1,11 +1,11 @@
--- ---
vault_version: "v2025.1.1" vault_version: "v2025.3.1"
vault_image_digest: "sha256:cb6b2095a4afc1d9d243a33f6d09211f40e3d82c7ae829fd025df5ff175a4918" vault_image_digest: "sha256:5b11739052c26dc3c2135b28dc5b072bc607f870a3e81fbbcc72e0cd1f124bcd"
# Cross Compile Docker Helper Scripts v1.6.1 # Cross Compile Docker Helper Scripts v1.6.1
# 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:9c207bead753dda9430bdd15425c6518fc7a03d866103c516a2c6889188f5894" xx_image_digest: "sha256:9c207bead753dda9430bdd15425c6518fc7a03d866103c516a2c6889188f5894"
rust_version: 1.84.1 # Rust version to be used rust_version: 1.86.0 # Rust version to be used
debian_version: bookworm # Debian release name to be used debian_version: bookworm # Debian release name to be used
alpine_version: "3.21" # Alpine version to be used alpine_version: "3.21" # 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
View File
@@ -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:v2025.1.1 # $ docker pull docker.io/vaultwarden/web-vault:v2025.3.1
# $ docker image inspect --format "{{.RepoDigests}}" docker.io/vaultwarden/web-vault:v2025.1.1 # $ docker image inspect --format "{{.RepoDigests}}" docker.io/vaultwarden/web-vault:v2025.3.1
# [docker.io/vaultwarden/web-vault@sha256:cb6b2095a4afc1d9d243a33f6d09211f40e3d82c7ae829fd025df5ff175a4918] # [docker.io/vaultwarden/web-vault@sha256:5b11739052c26dc3c2135b28dc5b072bc607f870a3e81fbbcc72e0cd1f124bcd]
# #
# - 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:cb6b2095a4afc1d9d243a33f6d09211f40e3d82c7ae829fd025df5ff175a4918 # $ docker image inspect --format "{{.RepoTags}}" docker.io/vaultwarden/web-vault@sha256:5b11739052c26dc3c2135b28dc5b072bc607f870a3e81fbbcc72e0cd1f124bcd
# [docker.io/vaultwarden/web-vault:v2025.1.1] # [docker.io/vaultwarden/web-vault:v2025.3.1]
# #
FROM --platform=linux/amd64 docker.io/vaultwarden/web-vault@sha256:cb6b2095a4afc1d9d243a33f6d09211f40e3d82c7ae829fd025df5ff175a4918 AS vault FROM --platform=linux/amd64 docker.io/vaultwarden/web-vault@sha256:5b11739052c26dc3c2135b28dc5b072bc607f870a3e81fbbcc72e0cd1f124bcd AS vault
########################## ALPINE BUILD IMAGES ########################## ########################## ALPINE BUILD IMAGES ##########################
## NOTE: The Alpine Base Images do not support other platforms then linux/amd64 ## NOTE: The Alpine Base Images do not support other platforms then linux/amd64
## 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=linux/amd64 ghcr.io/blackdex/rust-musl:x86_64-musl-stable-1.84.1 AS build_amd64 FROM --platform=linux/amd64 ghcr.io/blackdex/rust-musl:x86_64-musl-stable-1.86.0 AS build_amd64
FROM --platform=linux/amd64 ghcr.io/blackdex/rust-musl:aarch64-musl-stable-1.84.1 AS build_arm64 FROM --platform=linux/amd64 ghcr.io/blackdex/rust-musl:aarch64-musl-stable-1.86.0 AS build_arm64
FROM --platform=linux/amd64 ghcr.io/blackdex/rust-musl:armv7-musleabihf-stable-1.84.1 AS build_armv7 FROM --platform=linux/amd64 ghcr.io/blackdex/rust-musl:armv7-musleabihf-stable-1.86.0 AS build_armv7
FROM --platform=linux/amd64 ghcr.io/blackdex/rust-musl:arm-musleabi-stable-1.84.1 AS build_armv6 FROM --platform=linux/amd64 ghcr.io/blackdex/rust-musl:arm-musleabi-stable-1.86.0 AS build_armv6
########################## BUILD IMAGE ########################## ########################## BUILD IMAGE ##########################
# hadolint ignore=DL3006 # hadolint ignore=DL3006
+17 -17
View File
@@ -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:v2025.1.1 # $ docker pull docker.io/vaultwarden/web-vault:v2025.3.1
# $ docker image inspect --format "{{.RepoDigests}}" docker.io/vaultwarden/web-vault:v2025.1.1 # $ docker image inspect --format "{{.RepoDigests}}" docker.io/vaultwarden/web-vault:v2025.3.1
# [docker.io/vaultwarden/web-vault@sha256:cb6b2095a4afc1d9d243a33f6d09211f40e3d82c7ae829fd025df5ff175a4918] # [docker.io/vaultwarden/web-vault@sha256:5b11739052c26dc3c2135b28dc5b072bc607f870a3e81fbbcc72e0cd1f124bcd]
# #
# - 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:cb6b2095a4afc1d9d243a33f6d09211f40e3d82c7ae829fd025df5ff175a4918 # $ docker image inspect --format "{{.RepoTags}}" docker.io/vaultwarden/web-vault@sha256:5b11739052c26dc3c2135b28dc5b072bc607f870a3e81fbbcc72e0cd1f124bcd
# [docker.io/vaultwarden/web-vault:v2025.1.1] # [docker.io/vaultwarden/web-vault:v2025.3.1]
# #
FROM --platform=linux/amd64 docker.io/vaultwarden/web-vault@sha256:cb6b2095a4afc1d9d243a33f6d09211f40e3d82c7ae829fd025df5ff175a4918 AS vault FROM --platform=linux/amd64 docker.io/vaultwarden/web-vault@sha256:5b11739052c26dc3c2135b28dc5b072bc607f870a3e81fbbcc72e0cd1f124bcd 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:9c207bead753dda9430bd
########################## BUILD IMAGE ########################## ########################## BUILD IMAGE ##########################
# hadolint ignore=DL3006 # hadolint ignore=DL3006
FROM --platform=$BUILDPLATFORM docker.io/library/rust:1.84.1-slim-bookworm AS build FROM --platform=$BUILDPLATFORM docker.io/library/rust:1.86.0-slim-bookworm AS build
COPY --from=xx / / COPY --from=xx / /
ARG TARGETARCH ARG TARGETARCH
ARG TARGETVARIANT ARG TARGETVARIANT
@@ -89,24 +89,24 @@ RUN USER=root cargo new --bin /app
WORKDIR /app WORKDIR /app
# Environment variables for Cargo on Debian based builds # Environment variables for Cargo on Debian based builds
ARG ARCH_OPENSSL_LIB_DIR \ ARG TARGET_PKG_CONFIG_PATH
ARCH_OPENSSL_INCLUDE_DIR
RUN source /env-cargo && \ RUN source /env-cargo && \
if xx-info is-cross ; then \ if xx-info is-cross ; then \
# Some special variables if needed to override some build paths
if [[ -n "${ARCH_OPENSSL_LIB_DIR}" && -n "${ARCH_OPENSSL_INCLUDE_DIR}" ]]; then \
echo "export $(echo "${CARGO_TARGET}" | tr '[:lower:]' '[:upper:]' | tr - _)_OPENSSL_LIB_DIR=${ARCH_OPENSSL_LIB_DIR}" >> /env-cargo && \
echo "export $(echo "${CARGO_TARGET}" | tr '[:lower:]' '[:upper:]' | tr - _)_OPENSSL_INCLUDE_DIR=${ARCH_OPENSSL_INCLUDE_DIR}" >> /env-cargo ; \
fi && \
# We can't use xx-cargo since that uses clang, which doesn't work for our libraries. # We can't use xx-cargo since that uses clang, which doesn't work for our libraries.
# Because of this we generate the needed environment variables here which we can load in the needed steps. # Because of this we generate the needed environment variables here which we can load in the needed steps.
echo "export CC_$(echo "${CARGO_TARGET}" | tr '[:upper:]' '[:lower:]' | tr - _)=/usr/bin/$(xx-info)-gcc" >> /env-cargo && \ echo "export CC_$(echo "${CARGO_TARGET}" | tr '[:upper:]' '[:lower:]' | tr - _)=/usr/bin/$(xx-info)-gcc" >> /env-cargo && \
echo "export CARGO_TARGET_$(echo "${CARGO_TARGET}" | tr '[:lower:]' '[:upper:]' | tr - _)_LINKER=/usr/bin/$(xx-info)-gcc" >> /env-cargo && \ echo "export CARGO_TARGET_$(echo "${CARGO_TARGET}" | tr '[:lower:]' '[:upper:]' | tr - _)_LINKER=/usr/bin/$(xx-info)-gcc" >> /env-cargo && \
echo "export PKG_CONFIG=/usr/bin/$(xx-info)-pkg-config" >> /env-cargo && \
echo "export CROSS_COMPILE=1" >> /env-cargo && \ echo "export CROSS_COMPILE=1" >> /env-cargo && \
echo "export OPENSSL_INCLUDE_DIR=/usr/include/$(xx-info)" >> /env-cargo && \ echo "export PKG_CONFIG_ALLOW_CROSS=1" >> /env-cargo && \
echo "export OPENSSL_LIB_DIR=/usr/lib/$(xx-info)" >> /env-cargo ; \ # For some architectures `xx-info` returns a triple which doesn't matches the path on disk
# In those cases you can override this by setting the `TARGET_PKG_CONFIG_PATH` build-arg
if [[ -n "${TARGET_PKG_CONFIG_PATH}" ]]; then \
echo "export TARGET_PKG_CONFIG_PATH=${TARGET_PKG_CONFIG_PATH}" >> /env-cargo ; \
else \
echo "export PKG_CONFIG_PATH=/usr/lib/$(xx-info)/pkgconfig" >> /env-cargo ; \
fi && \
echo "# End of env-cargo" >> /env-cargo ; \
fi && \ fi && \
# Output the current contents of the file # Output the current contents of the file
cat /env-cargo cat /env-cargo
+10 -10
View File
@@ -109,24 +109,24 @@ WORKDIR /app
{% if base == "debian" %} {% if base == "debian" %}
# Environment variables for Cargo on Debian based builds # Environment variables for Cargo on Debian based builds
ARG ARCH_OPENSSL_LIB_DIR \ ARG TARGET_PKG_CONFIG_PATH
ARCH_OPENSSL_INCLUDE_DIR
RUN source /env-cargo && \ RUN source /env-cargo && \
if xx-info is-cross ; then \ if xx-info is-cross ; then \
# Some special variables if needed to override some build paths
if [[ -n "${ARCH_OPENSSL_LIB_DIR}" && -n "${ARCH_OPENSSL_INCLUDE_DIR}" ]]; then \
echo "export $(echo "${CARGO_TARGET}" | tr '[:lower:]' '[:upper:]' | tr - _)_OPENSSL_LIB_DIR=${ARCH_OPENSSL_LIB_DIR}" >> /env-cargo && \
echo "export $(echo "${CARGO_TARGET}" | tr '[:lower:]' '[:upper:]' | tr - _)_OPENSSL_INCLUDE_DIR=${ARCH_OPENSSL_INCLUDE_DIR}" >> /env-cargo ; \
fi && \
# We can't use xx-cargo since that uses clang, which doesn't work for our libraries. # We can't use xx-cargo since that uses clang, which doesn't work for our libraries.
# Because of this we generate the needed environment variables here which we can load in the needed steps. # Because of this we generate the needed environment variables here which we can load in the needed steps.
echo "export CC_$(echo "${CARGO_TARGET}" | tr '[:upper:]' '[:lower:]' | tr - _)=/usr/bin/$(xx-info)-gcc" >> /env-cargo && \ echo "export CC_$(echo "${CARGO_TARGET}" | tr '[:upper:]' '[:lower:]' | tr - _)=/usr/bin/$(xx-info)-gcc" >> /env-cargo && \
echo "export CARGO_TARGET_$(echo "${CARGO_TARGET}" | tr '[:lower:]' '[:upper:]' | tr - _)_LINKER=/usr/bin/$(xx-info)-gcc" >> /env-cargo && \ echo "export CARGO_TARGET_$(echo "${CARGO_TARGET}" | tr '[:lower:]' '[:upper:]' | tr - _)_LINKER=/usr/bin/$(xx-info)-gcc" >> /env-cargo && \
echo "export PKG_CONFIG=/usr/bin/$(xx-info)-pkg-config" >> /env-cargo && \
echo "export CROSS_COMPILE=1" >> /env-cargo && \ echo "export CROSS_COMPILE=1" >> /env-cargo && \
echo "export OPENSSL_INCLUDE_DIR=/usr/include/$(xx-info)" >> /env-cargo && \ echo "export PKG_CONFIG_ALLOW_CROSS=1" >> /env-cargo && \
echo "export OPENSSL_LIB_DIR=/usr/lib/$(xx-info)" >> /env-cargo ; \ # For some architectures `xx-info` returns a triple which doesn't matches the path on disk
# In those cases you can override this by setting the `TARGET_PKG_CONFIG_PATH` build-arg
if [[ -n "${TARGET_PKG_CONFIG_PATH}" ]]; then \
echo "export TARGET_PKG_CONFIG_PATH=${TARGET_PKG_CONFIG_PATH}" >> /env-cargo ; \
else \
echo "export PKG_CONFIG_PATH=/usr/lib/$(xx-info)/pkgconfig" >> /env-cargo ; \
fi && \
echo "# End of env-cargo" >> /env-cargo ; \
fi && \ fi && \
# Output the current contents of the file # Output the current contents of the file
cat /env-cargo cat /env-cargo
+1 -10
View File
@@ -133,8 +133,7 @@ target "debian-386" {
platforms = ["linux/386"] platforms = ["linux/386"]
tags = generate_tags("", "-386") tags = generate_tags("", "-386")
args = { args = {
ARCH_OPENSSL_LIB_DIR = "/usr/lib/i386-linux-gnu" TARGET_PKG_CONFIG_PATH = "/usr/lib/i386-linux-gnu/pkgconfig"
ARCH_OPENSSL_INCLUDE_DIR = "/usr/include/i386-linux-gnu"
} }
} }
@@ -142,20 +141,12 @@ target "debian-ppc64le" {
inherits = ["debian"] inherits = ["debian"]
platforms = ["linux/ppc64le"] platforms = ["linux/ppc64le"]
tags = generate_tags("", "-ppc64le") tags = generate_tags("", "-ppc64le")
args = {
ARCH_OPENSSL_LIB_DIR = "/usr/lib/powerpc64le-linux-gnu"
ARCH_OPENSSL_INCLUDE_DIR = "/usr/include/powerpc64le-linux-gnu"
}
} }
target "debian-s390x" { target "debian-s390x" {
inherits = ["debian"] inherits = ["debian"]
platforms = ["linux/s390x"] platforms = ["linux/s390x"]
tags = generate_tags("", "-s390x") tags = generate_tags("", "-s390x")
args = {
ARCH_OPENSSL_LIB_DIR = "/usr/lib/s390x-linux-gnu"
ARCH_OPENSSL_INCLUDE_DIR = "/usr/include/s390x-linux-gnu"
}
} }
// ==== End of unsupported Debian architecture targets === // ==== End of unsupported Debian architecture targets ===
+5 -2
View File
@@ -9,5 +9,8 @@ path = "src/lib.rs"
proc-macro = true proc-macro = true
[dependencies] [dependencies]
quote = "1.0.38" quote = "1.0.40"
syn = "2.0.98" syn = "2.0.100"
[lints]
workspace = true
+4 -6
View File
@@ -1,5 +1,3 @@
extern crate proc_macro;
use proc_macro::TokenStream; use proc_macro::TokenStream;
use quote::quote; use quote::quote;
@@ -12,7 +10,7 @@ pub fn derive_uuid_from_param(input: TokenStream) -> TokenStream {
fn impl_derive_uuid_macro(ast: &syn::DeriveInput) -> TokenStream { fn impl_derive_uuid_macro(ast: &syn::DeriveInput) -> TokenStream {
let name = &ast.ident; let name = &ast.ident;
let gen = quote! { let gen_derive = quote! {
#[automatically_derived] #[automatically_derived]
impl<'r> rocket::request::FromParam<'r> for #name { impl<'r> rocket::request::FromParam<'r> for #name {
type Error = (); type Error = ();
@@ -27,7 +25,7 @@ fn impl_derive_uuid_macro(ast: &syn::DeriveInput) -> TokenStream {
} }
} }
}; };
gen.into() gen_derive.into()
} }
#[proc_macro_derive(IdFromParam)] #[proc_macro_derive(IdFromParam)]
@@ -39,7 +37,7 @@ pub fn derive_id_from_param(input: TokenStream) -> TokenStream {
fn impl_derive_safestring_macro(ast: &syn::DeriveInput) -> TokenStream { fn impl_derive_safestring_macro(ast: &syn::DeriveInput) -> TokenStream {
let name = &ast.ident; let name = &ast.ident;
let gen = quote! { let gen_derive = quote! {
#[automatically_derived] #[automatically_derived]
impl<'r> rocket::request::FromParam<'r> for #name { impl<'r> rocket::request::FromParam<'r> for #name {
type Error = (); type Error = ();
@@ -54,5 +52,5 @@ fn impl_derive_safestring_macro(ast: &syn::DeriveInput) -> TokenStream {
} }
} }
}; };
gen.into() gen_derive.into()
} }
+1 -1
View File
@@ -1,4 +1,4 @@
[toolchain] [toolchain]
channel = "1.84.1" channel = "1.86.0"
components = [ "rustfmt", "clippy" ] components = [ "rustfmt", "clippy" ]
profile = "minimal" profile = "minimal"
+1 -1
View File
@@ -618,7 +618,7 @@ async fn has_http_access() -> bool {
use cached::proc_macro::cached; 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. /// 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 300 seconds (5 minutes) which should prevent the exhaustion of the rate limit. /// It will cache this function for 300 seconds (5 minutes) which should prevent the exhaustion of the rate limit.
#[cached(time = 300, sync_writes = true)] #[cached(time = 300, sync_writes = "default")]
async fn get_release_info(has_http_access: bool, running_within_container: bool) -> (String, String, String) { async fn get_release_info(has_http_access: bool, running_within_container: bool) -> (String, String, String) {
// If the HTTP Check failed, do not even attempt to check for new versions since we were not able to connect with github.com anyway. // If the HTTP Check failed, do not even attempt to check for new versions since we were not able to connect with github.com anyway.
if has_http_access { if has_http_access {
+95 -13
View File
@@ -70,18 +70,31 @@ pub fn routes() -> Vec<rocket::Route> {
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct RegisterData { pub struct RegisterData {
email: String, email: String,
kdf: Option<i32>, kdf: Option<i32>,
kdf_iterations: Option<i32>, kdf_iterations: Option<i32>,
kdf_memory: Option<i32>, kdf_memory: Option<i32>,
kdf_parallelism: Option<i32>, kdf_parallelism: Option<i32>,
#[serde(alias = "userSymmetricKey")]
key: String, key: String,
#[serde(alias = "userAsymmetricKeys")]
keys: Option<KeysData>, keys: Option<KeysData>,
master_password_hash: String, master_password_hash: String,
master_password_hint: Option<String>, master_password_hint: Option<String>,
name: Option<String>, name: Option<String>,
token: Option<String>,
#[allow(dead_code)] #[allow(dead_code)]
organization_user_id: Option<MembershipId>, organization_user_id: Option<MembershipId>,
// Used only from the register/finish endpoint
email_verification_token: Option<String>,
accept_emergency_access_id: Option<EmergencyAccessId>,
accept_emergency_access_invite_token: Option<String>,
#[serde(alias = "token")]
org_invite_token: Option<String>,
} }
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
@@ -124,13 +137,78 @@ async fn is_email_2fa_required(member_id: Option<MembershipId>, conn: &mut DbCon
#[post("/accounts/register", data = "<data>")] #[post("/accounts/register", data = "<data>")]
async fn register(data: Json<RegisterData>, conn: DbConn) -> JsonResult { async fn register(data: Json<RegisterData>, conn: DbConn) -> JsonResult {
_register(data, conn).await _register(data, false, conn).await
} }
pub async fn _register(data: Json<RegisterData>, mut conn: DbConn) -> JsonResult { pub async fn _register(data: Json<RegisterData>, email_verification: bool, mut conn: DbConn) -> JsonResult {
let data: RegisterData = data.into_inner(); let mut data: RegisterData = data.into_inner();
let email = data.email.to_lowercase(); let email = data.email.to_lowercase();
let mut email_verified = false;
let mut pending_emergency_access = None;
// First, validate the provided verification tokens
if email_verification {
match (
&data.email_verification_token,
&data.accept_emergency_access_id,
&data.accept_emergency_access_invite_token,
&data.organization_user_id,
&data.org_invite_token,
) {
// Normal user registration, when email verification is required
(Some(email_verification_token), None, None, None, None) => {
let claims = crate::auth::decode_register_verify(email_verification_token)?;
if claims.sub != data.email {
err!("Email verification token does not match email");
}
// During this call we don't get the name, so extract it from the claims
if claims.name.is_some() {
data.name = claims.name;
}
email_verified = claims.verified;
}
// Emergency access registration
(None, Some(accept_emergency_access_id), Some(accept_emergency_access_invite_token), None, None) => {
if !CONFIG.emergency_access_allowed() {
err!("Emergency access is not enabled.")
}
let claims = crate::auth::decode_emergency_access_invite(accept_emergency_access_invite_token)?;
if claims.email != data.email {
err!("Claim email does not match email")
}
if &claims.emer_id != accept_emergency_access_id {
err!("Claim emer_id does not match accept_emergency_access_id")
}
pending_emergency_access = Some((accept_emergency_access_id, claims));
email_verified = true;
}
// Org invite
(None, None, None, Some(organization_user_id), Some(org_invite_token)) => {
let claims = decode_invite(org_invite_token)?;
if claims.email != data.email {
err!("Claim email does not match email")
}
if &claims.member_id != organization_user_id {
err!("Claim org_user_id does not match organization_user_id")
}
email_verified = true;
}
_ => {
err!("Registration is missing required parameters")
}
}
}
// Check if the length of the username exceeds 50 characters (Same is Upstream Bitwarden) // Check if the length of the username exceeds 50 characters (Same is Upstream Bitwarden)
// This also prevents issues with very long usernames causing to large JWT's. See #2419 // This also prevents issues with very long usernames causing to large JWT's. See #2419
if let Some(ref name) = data.name { if let Some(ref name) = data.name {
@@ -144,20 +222,17 @@ pub async fn _register(data: Json<RegisterData>, mut conn: DbConn) -> JsonResult
let password_hint = clean_password_hint(&data.master_password_hint); let password_hint = clean_password_hint(&data.master_password_hint);
enforce_password_hint_setting(&password_hint)?; enforce_password_hint_setting(&password_hint)?;
let mut verified_by_invite = false;
let mut user = match User::find_by_mail(&email, &mut conn).await { let mut user = match User::find_by_mail(&email, &mut conn).await {
Some(mut user) => { Some(user) => {
if !user.password_hash.is_empty() { if !user.password_hash.is_empty() {
err!("Registration not allowed or user already exists") err!("Registration not allowed or user already exists")
} }
if let Some(token) = data.token { if let Some(token) = data.org_invite_token {
let claims = decode_invite(&token)?; let claims = decode_invite(&token)?;
if claims.email == email { if claims.email == email {
// Verify the email address when signing up via a valid invite token // Verify the email address when signing up via a valid invite token
verified_by_invite = true; email_verified = true;
user.verified_at = Some(Utc::now().naive_utc());
user user
} else { } else {
err!("Registration email does not match invite email") err!("Registration email does not match invite email")
@@ -181,7 +256,10 @@ pub async fn _register(data: Json<RegisterData>, mut conn: DbConn) -> JsonResult
// Order is important here; the invitation check must come first // Order is important here; the invitation check must come first
// because the vaultwarden admin can invite anyone, regardless // because the vaultwarden admin can invite anyone, regardless
// of other signup restrictions. // of other signup restrictions.
if Invitation::take(&email, &mut conn).await || CONFIG.is_signup_allowed(&email) { if Invitation::take(&email, &mut conn).await
|| CONFIG.is_signup_allowed(&email)
|| pending_emergency_access.is_some()
{
User::new(email.clone()) User::new(email.clone())
} else { } else {
err!("Registration not allowed or user already exists") err!("Registration not allowed or user already exists")
@@ -216,8 +294,12 @@ pub async fn _register(data: Json<RegisterData>, mut conn: DbConn) -> JsonResult
user.public_key = Some(keys.public_key); user.public_key = Some(keys.public_key);
} }
if email_verified {
user.verified_at = Some(Utc::now().naive_utc());
}
if CONFIG.mail_enabled() { if CONFIG.mail_enabled() {
if CONFIG.signups_verify() && !verified_by_invite { if CONFIG.signups_verify() && !email_verified {
if let Err(e) = mail::send_welcome_must_verify(&user.email, &user.uuid).await { if let Err(e) = mail::send_welcome_must_verify(&user.email, &user.uuid).await {
error!("Error sending welcome email: {:#?}", e); error!("Error sending welcome email: {:#?}", e);
} }
@@ -226,7 +308,7 @@ pub async fn _register(data: Json<RegisterData>, mut conn: DbConn) -> JsonResult
error!("Error sending welcome email: {:#?}", e); error!("Error sending welcome email: {:#?}", e);
} }
if verified_by_invite && is_email_2fa_required(data.organization_user_id, &mut conn).await { if email_verified && is_email_2fa_required(data.organization_user_id, &mut conn).await {
email::activate_email_2fa(&user, &mut conn).await.ok(); email::activate_email_2fa(&user, &mut conn).await.ok();
} }
} }
+9 -8
View File
@@ -1376,7 +1376,7 @@ async fn delete_attachment_post_admin(
headers: Headers, headers: Headers,
conn: DbConn, conn: DbConn,
nt: Notify<'_>, nt: Notify<'_>,
) -> EmptyResult { ) -> JsonResult {
delete_attachment(cipher_id, attachment_id, headers, conn, nt).await delete_attachment(cipher_id, attachment_id, headers, conn, nt).await
} }
@@ -1387,7 +1387,7 @@ async fn delete_attachment_post(
headers: Headers, headers: Headers,
conn: DbConn, conn: DbConn,
nt: Notify<'_>, nt: Notify<'_>,
) -> EmptyResult { ) -> JsonResult {
delete_attachment(cipher_id, attachment_id, headers, conn, nt).await delete_attachment(cipher_id, attachment_id, headers, conn, nt).await
} }
@@ -1398,7 +1398,7 @@ async fn delete_attachment(
headers: Headers, headers: Headers,
mut conn: DbConn, mut conn: DbConn,
nt: Notify<'_>, nt: Notify<'_>,
) -> EmptyResult { ) -> JsonResult {
_delete_cipher_attachment_by_id(&cipher_id, &attachment_id, &headers, &mut conn, &nt).await _delete_cipher_attachment_by_id(&cipher_id, &attachment_id, &headers, &mut conn, &nt).await
} }
@@ -1409,7 +1409,7 @@ async fn delete_attachment_admin(
headers: Headers, headers: Headers,
mut conn: DbConn, mut conn: DbConn,
nt: Notify<'_>, nt: Notify<'_>,
) -> EmptyResult { ) -> JsonResult {
_delete_cipher_attachment_by_id(&cipher_id, &attachment_id, &headers, &mut conn, &nt).await _delete_cipher_attachment_by_id(&cipher_id, &attachment_id, &headers, &mut conn, &nt).await
} }
@@ -1818,7 +1818,7 @@ async fn _delete_cipher_attachment_by_id(
headers: &Headers, headers: &Headers,
conn: &mut DbConn, conn: &mut DbConn,
nt: &Notify<'_>, nt: &Notify<'_>,
) -> EmptyResult { ) -> JsonResult {
let Some(attachment) = Attachment::find_by_id(attachment_id, conn).await else { let Some(attachment) = Attachment::find_by_id(attachment_id, conn).await else {
err!("Attachment doesn't exist") err!("Attachment doesn't exist")
}; };
@@ -1847,11 +1847,11 @@ async fn _delete_cipher_attachment_by_id(
) )
.await; .await;
if let Some(org_id) = cipher.organization_uuid { if let Some(ref org_id) = cipher.organization_uuid {
log_event( log_event(
EventType::CipherAttachmentDeleted as i32, EventType::CipherAttachmentDeleted as i32,
&cipher.uuid, &cipher.uuid,
&org_id, org_id,
&headers.user.uuid, &headers.user.uuid,
headers.device.atype, headers.device.atype,
&headers.ip.ip, &headers.ip.ip,
@@ -1859,7 +1859,8 @@ async fn _delete_cipher_attachment_by_id(
) )
.await; .await;
} }
Ok(()) let cipher_json = cipher.to_json(&headers.host, &headers.user.uuid, None, CipherSyncType::User, conn).await;
Ok(Json(json!({"cipher":cipher_json})))
} }
/// This will hold all the necessary data to improve a full sync of all the ciphers /// This will hold all the necessary data to improve a full sync of all the ciphers
+3
View File
@@ -205,6 +205,9 @@ fn config() -> Json<Value> {
feature_states.insert("key-rotation-improvements".to_string(), true); feature_states.insert("key-rotation-improvements".to_string(), true);
feature_states.insert("flexible-collections-v-1".to_string(), false); feature_states.insert("flexible-collections-v-1".to_string(), false);
feature_states.insert("email-verification".to_string(), true);
feature_states.insert("unauth-ui-refresh".to_string(), true);
Json(json!({ Json(json!({
// Note: The clients use this version to handle backwards compatibility concerns // Note: The clients use this version to handle backwards compatibility concerns
// This means they expect a version that closely matches the Bitwarden server version // This means they expect a version that closely matches the Bitwarden server version
+14 -23
View File
@@ -997,8 +997,6 @@ struct InviteData {
r#type: NumberOrString, r#type: NumberOrString,
collections: Option<Vec<CollectionData>>, collections: Option<Vec<CollectionData>>,
#[serde(default)] #[serde(default)]
access_all: bool,
#[serde(default)]
permissions: HashMap<String, Value>, permissions: HashMap<String, Value>,
} }
@@ -1012,7 +1010,7 @@ async fn send_invite(
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");
} }
let mut data: InviteData = data.into_inner(); let data: InviteData = data.into_inner();
// HACK: We need the raw user-type to be sure custom role is selected to determine the access_all permission // HACK: We need the raw user-type to be sure custom role is selected to determine the access_all permission
// The from_str() will convert the custom role type into a manager role type // The from_str() will convert the custom role type into a manager role type
@@ -1030,13 +1028,11 @@ async fn send_invite(
// HACK: This converts the Custom role which has the `Manage all collections` box checked into an access_all flag // HACK: This converts the Custom role which has the `Manage all collections` box checked into an access_all flag
// Since the parent checkbox is not sent to the server we need to check and verify the child checkboxes // Since the parent checkbox is not sent to the server we need to check and verify the child checkboxes
// If the box is not checked, the user will still be a manager, but not with the access_all permission // If the box is not checked, the user will still be a manager, but not with the access_all permission
if raw_type.eq("4") let access_all = new_type >= MembershipType::Admin
&& data.permissions.get("editAnyCollection") == Some(&json!(true)) || (raw_type.eq("4")
&& data.permissions.get("deleteAnyCollection") == Some(&json!(true)) && data.permissions.get("editAnyCollection") == Some(&json!(true))
&& data.permissions.get("createNewCollections") == Some(&json!(true)) && data.permissions.get("deleteAnyCollection") == Some(&json!(true))
{ && data.permissions.get("createNewCollections") == Some(&json!(true)));
data.access_all = true;
}
let mut user_created: bool = false; let mut user_created: bool = false;
for email in data.emails.iter() { for email in data.emails.iter() {
@@ -1074,7 +1070,6 @@ async fn send_invite(
}; };
let mut new_member = Membership::new(user.uuid.clone(), org_id.clone()); let mut new_member = Membership::new(user.uuid.clone(), org_id.clone());
let access_all = data.access_all;
new_member.access_all = access_all; new_member.access_all = access_all;
new_member.atype = new_type; new_member.atype = new_type;
new_member.status = member_status; new_member.status = member_status;
@@ -1525,8 +1520,6 @@ struct EditUserData {
collections: Option<Vec<CollectionData>>, collections: Option<Vec<CollectionData>>,
groups: Option<Vec<GroupId>>, groups: Option<Vec<GroupId>>,
#[serde(default)] #[serde(default)]
access_all: bool,
#[serde(default)]
permissions: HashMap<String, Value>, permissions: HashMap<String, Value>,
} }
@@ -1552,7 +1545,7 @@ async fn edit_member(
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");
} }
let mut data: EditUserData = data.into_inner(); let data: EditUserData = data.into_inner();
// HACK: We need the raw user-type to be sure custom role is selected to determine the access_all permission // HACK: We need the raw user-type to be sure custom role is selected to determine the access_all permission
// The from_str() will convert the custom role type into a manager role type // The from_str() will convert the custom role type into a manager role type
@@ -1565,13 +1558,11 @@ async fn edit_member(
// HACK: This converts the Custom role which has the `Manage all collections` box checked into an access_all flag // HACK: This converts the Custom role which has the `Manage all collections` box checked into an access_all flag
// Since the parent checkbox is not sent to the server we need to check and verify the child checkboxes // Since the parent checkbox is not sent to the server we need to check and verify the child checkboxes
// If the box is not checked, the user will still be a manager, but not with the access_all permission // If the box is not checked, the user will still be a manager, but not with the access_all permission
if raw_type.eq("4") let access_all = new_type >= MembershipType::Admin
&& data.permissions.get("editAnyCollection") == Some(&json!(true)) || (raw_type.eq("4")
&& data.permissions.get("deleteAnyCollection") == Some(&json!(true)) && data.permissions.get("editAnyCollection") == Some(&json!(true))
&& data.permissions.get("createNewCollections") == Some(&json!(true)) && data.permissions.get("deleteAnyCollection") == Some(&json!(true))
{ && data.permissions.get("createNewCollections") == Some(&json!(true)));
data.access_all = true;
}
let mut member_to_edit = match Membership::find_by_uuid_and_org(&member_id, &org_id, &mut conn).await { let mut member_to_edit = match Membership::find_by_uuid_and_org(&member_id, &org_id, &mut conn).await {
Some(member) => member, Some(member) => member,
@@ -1617,7 +1608,7 @@ async fn edit_member(
} }
} }
member_to_edit.access_all = data.access_all; member_to_edit.access_all = access_all;
member_to_edit.atype = new_type as i32; member_to_edit.atype = new_type as i32;
// Delete all the odd collections // Delete all the odd collections
@@ -1626,7 +1617,7 @@ async fn edit_member(
} }
// If no accessAll, add the collections received // If no accessAll, add the collections received
if !data.access_all { if !access_all {
for col in data.collections.iter().flatten() { for col in data.collections.iter().flatten() {
match Collection::find_by_uuid_and_org(&col.id, &org_id, &mut conn).await { match Collection::find_by_uuid_and_org(&col.id, &org_id, &mut conn).await {
None => err!("Collection not found in Organization"), None => err!("Collection not found in Organization"),
+5 -1
View File
@@ -378,7 +378,11 @@ async fn post_send_file_v2_data(
}; };
match data.data.raw_name() { match data.data.raw_name() {
Some(raw_file_name) if raw_file_name.dangerous_unsafe_unsanitized_raw() == send_data.fileName => (), Some(raw_file_name)
if raw_file_name.dangerous_unsafe_unsanitized_raw() == send_data.fileName
// be less strict only if using CLI, cf. https://github.com/dani-garcia/vaultwarden/issues/5614
|| (headers.device.is_cli() && send_data.fileName.ends_with(raw_file_name.dangerous_unsafe_unsanitized_raw().as_str())
) => {}
Some(raw_file_name) => err!( Some(raw_file_name) => err!(
"Send file name does not match.", "Send file name does not match.",
format!( format!(
+9 -3
View File
@@ -197,14 +197,20 @@ async fn email(data: Json<EmailData>, headers: Headers, mut conn: DbConn) -> Jso
} }
/// Validate the email code when used as TwoFactor token mechanism /// Validate the email code when used as TwoFactor token mechanism
pub async fn validate_email_code_str(user_id: &UserId, token: &str, data: &str, conn: &mut DbConn) -> EmptyResult { pub async fn validate_email_code_str(
user_id: &UserId,
token: &str,
data: &str,
ip: &std::net::IpAddr,
conn: &mut DbConn,
) -> EmptyResult {
let mut email_data = EmailTokenData::from_json(data)?; let mut email_data = EmailTokenData::from_json(data)?;
let mut twofactor = TwoFactor::find_by_user_and_type(user_id, TwoFactorType::Email as i32, conn) let mut twofactor = TwoFactor::find_by_user_and_type(user_id, TwoFactorType::Email as i32, conn)
.await .await
.map_res("Two factor not found")?; .map_res("Two factor not found")?;
let Some(issued_token) = &email_data.last_token else { let Some(issued_token) = &email_data.last_token else {
err!( err!(
"No token available", format!("No token available! IP: {}", ip),
ErrorEvent { ErrorEvent {
event: EventType::UserFailedLogIn2fa event: EventType::UserFailedLogIn2fa
} }
@@ -220,7 +226,7 @@ pub async fn validate_email_code_str(user_id: &UserId, token: &str, data: &str,
twofactor.save(conn).await?; twofactor.save(conn).await?;
err!( err!(
"Token is invalid", format!("Token is invalid! IP: {}", ip),
ErrorEvent { ErrorEvent {
event: EventType::UserFailedLogIn2fa event: EventType::UserFailedLogIn2fa
} }
+62 -3
View File
@@ -24,7 +24,7 @@ use crate::{
}; };
pub fn routes() -> Vec<Route> { pub fn routes() -> Vec<Route> {
routes![login, prelogin, identity_register] routes![login, prelogin, identity_register, register_verification_email, register_finish]
} }
#[post("/connect/token", data = "<data>")] #[post("/connect/token", data = "<data>")]
@@ -575,7 +575,7 @@ async fn twofactor_auth(
} }
} }
Some(TwoFactorType::Email) => { Some(TwoFactorType::Email) => {
email::validate_email_code_str(&user.uuid, twofactor_code, &selected_data?, conn).await? email::validate_email_code_str(&user.uuid, twofactor_code, &selected_data?, &ip.ip, conn).await?
} }
Some(TwoFactorType::Remember) => { Some(TwoFactorType::Remember) => {
@@ -714,7 +714,66 @@ async fn prelogin(data: Json<PreloginData>, conn: DbConn) -> Json<Value> {
#[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, conn).await _register(data, false, conn).await
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct RegisterVerificationData {
email: String,
name: Option<String>,
// receiveMarketingEmails: bool,
}
#[derive(rocket::Responder)]
enum RegisterVerificationResponse {
NoContent(()),
Token(Json<String>),
}
#[post("/accounts/register/send-verification-email", data = "<data>")]
async fn register_verification_email(
data: Json<RegisterVerificationData>,
mut conn: DbConn,
) -> ApiResult<RegisterVerificationResponse> {
let data = data.into_inner();
if !CONFIG.is_signup_allowed(&data.email) {
err!("Registration not allowed or user already exists")
}
let should_send_mail = CONFIG.mail_enabled() && CONFIG.signups_verify();
let token_claims =
crate::auth::generate_register_verify_claims(data.email.clone(), data.name.clone(), should_send_mail);
let token = crate::auth::encode_jwt(&token_claims);
if should_send_mail {
let user = User::find_by_mail(&data.email, &mut conn).await;
if user.filter(|u| u.private_key.is_some()).is_some() {
// There is still a timing side channel here in that the code
// paths that send mail take noticeably longer than ones that
// don't. Add a randomized sleep to mitigate this somewhat.
use rand::{rngs::SmallRng, Rng, SeedableRng};
let mut rng = SmallRng::from_os_rng();
let delta: i32 = 100;
let sleep_ms = (1_000 + rng.random_range(-delta..=delta)) as u64;
tokio::time::sleep(tokio::time::Duration::from_millis(sleep_ms)).await;
} else {
mail::send_register_verify_email(&data.email, &token).await?;
}
Ok(RegisterVerificationResponse::NoContent(()))
} else {
// If email verification is not required, return the token directly
// the clients will use this token to finish the registration
Ok(RegisterVerificationResponse::Token(Json(token)))
}
}
#[post("/accounts/register/finish", data = "<data>")]
async fn register_finish(data: Json<RegisterData>, conn: DbConn) -> JsonResult {
_register(data, true, conn).await
} }
// https://github.com/bitwarden/jslib/blob/master/common/src/models/request/tokenRequest.ts // https://github.com/bitwarden/jslib/blob/master/common/src/models/request/tokenRequest.ts
+3 -3
View File
@@ -495,7 +495,7 @@ impl WebSocketUsers {
pub async fn send_auth_request( pub async fn send_auth_request(
&self, &self,
user_id: &UserId, user_id: &UserId,
auth_request_uuid: &String, auth_request_uuid: &str,
acting_device_id: &DeviceId, acting_device_id: &DeviceId,
conn: &mut DbConn, conn: &mut DbConn,
) { ) {
@@ -504,7 +504,7 @@ impl WebSocketUsers {
return; return;
} }
let data = create_update( let data = create_update(
vec![("Id".into(), auth_request_uuid.clone().into()), ("UserId".into(), user_id.to_string().into())], vec![("Id".into(), auth_request_uuid.to_owned().into()), ("UserId".into(), user_id.to_string().into())],
UpdateType::AuthRequest, UpdateType::AuthRequest,
Some(acting_device_id.clone()), Some(acting_device_id.clone()),
); );
@@ -513,7 +513,7 @@ impl WebSocketUsers {
} }
if CONFIG.push_enabled() { if CONFIG.push_enabled() {
push_auth_request(user_id.clone(), auth_request_uuid.to_string(), conn).await; push_auth_request(user_id.clone(), auth_request_uuid.to_owned(), conn).await;
} }
} }
+1 -1
View File
@@ -57,7 +57,7 @@ fn vaultwarden_css() -> Cached<Css<String>> {
let css_options = json!({ let css_options = json!({
"signup_disabled": !CONFIG.signups_allowed() && CONFIG.signups_domains_whitelist().is_empty(), "signup_disabled": !CONFIG.signups_allowed() && CONFIG.signups_domains_whitelist().is_empty(),
"mail_enabled": CONFIG.mail_enabled(), "mail_enabled": CONFIG.mail_enabled(),
"yubico_enabled": CONFIG._enable_yubico() && (CONFIG.yubico_client_id().is_some() == CONFIG.yubico_secret_key().is_some()), "yubico_enabled": CONFIG._enable_yubico() && CONFIG.yubico_client_id().is_some() && CONFIG.yubico_secret_key().is_some(),
"emergency_access_allowed": CONFIG.emergency_access_allowed(), "emergency_access_allowed": CONFIG.emergency_access_allowed(),
"sends_allowed": CONFIG.sends_allowed(), "sends_allowed": CONFIG.sends_allowed(),
"load_user_scss": true, "load_user_scss": true,
+32
View File
@@ -35,6 +35,7 @@ static JWT_ADMIN_ISSUER: Lazy<String> = Lazy::new(|| format!("{}|admin", CONFIG.
static JWT_SEND_ISSUER: Lazy<String> = Lazy::new(|| format!("{}|send", CONFIG.domain_origin())); static JWT_SEND_ISSUER: Lazy<String> = Lazy::new(|| format!("{}|send", CONFIG.domain_origin()));
static JWT_ORG_API_KEY_ISSUER: Lazy<String> = Lazy::new(|| format!("{}|api.organization", CONFIG.domain_origin())); static JWT_ORG_API_KEY_ISSUER: Lazy<String> = Lazy::new(|| format!("{}|api.organization", CONFIG.domain_origin()));
static JWT_FILE_DOWNLOAD_ISSUER: Lazy<String> = Lazy::new(|| format!("{}|file_download", CONFIG.domain_origin())); static JWT_FILE_DOWNLOAD_ISSUER: Lazy<String> = Lazy::new(|| format!("{}|file_download", CONFIG.domain_origin()));
static JWT_REGISTER_VERIFY_ISSUER: Lazy<String> = Lazy::new(|| format!("{}|register_verify", CONFIG.domain_origin()));
static PRIVATE_RSA_KEY: OnceCell<EncodingKey> = OnceCell::new(); static PRIVATE_RSA_KEY: OnceCell<EncodingKey> = OnceCell::new();
static PUBLIC_RSA_KEY: OnceCell<DecodingKey> = OnceCell::new(); static PUBLIC_RSA_KEY: OnceCell<DecodingKey> = OnceCell::new();
@@ -145,6 +146,10 @@ pub fn decode_file_download(token: &str) -> Result<FileDownloadClaims, Error> {
decode_jwt(token, JWT_FILE_DOWNLOAD_ISSUER.to_string()) decode_jwt(token, JWT_FILE_DOWNLOAD_ISSUER.to_string())
} }
pub fn decode_register_verify(token: &str) -> Result<RegisterVerifyClaims, Error> {
decode_jwt(token, JWT_REGISTER_VERIFY_ISSUER.to_string())
}
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub struct LoginJwtClaims { pub struct LoginJwtClaims {
// Not before // Not before
@@ -315,6 +320,33 @@ pub fn generate_file_download_claims(cipher_id: CipherId, file_id: AttachmentId)
} }
} }
#[derive(Debug, Serialize, Deserialize)]
pub struct RegisterVerifyClaims {
// Not before
pub nbf: i64,
// Expiration time
pub exp: i64,
// Issuer
pub iss: String,
// Subject
pub sub: String,
pub name: Option<String>,
pub verified: bool,
}
pub fn generate_register_verify_claims(email: String, name: Option<String>, verified: bool) -> RegisterVerifyClaims {
let time_now = Utc::now();
RegisterVerifyClaims {
nbf: time_now.timestamp(),
exp: (time_now + TimeDelta::try_minutes(30).unwrap()).timestamp(),
iss: JWT_REGISTER_VERIFY_ISSUER.to_string(),
sub: email,
name,
verified,
}
}
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub struct BasicJwtClaims { pub struct BasicJwtClaims {
// Not before // Not before
+11 -5
View File
@@ -104,7 +104,7 @@ macro_rules! make_config {
let mut builder = ConfigBuilder::default(); let mut builder = ConfigBuilder::default();
$($( $($(
builder.$name = make_config! { @getenv paste::paste!(stringify!([<$name:upper>])), $ty }; builder.$name = make_config! { @getenv pastey::paste!(stringify!([<$name:upper>])), $ty };
)+)+ )+)+
builder builder
@@ -133,7 +133,7 @@ macro_rules! make_config {
builder.$name = v.clone(); builder.$name = v.clone();
if self.$name.is_some() { if self.$name.is_some() {
overrides.push(paste::paste!(stringify!([<$name:upper>])).into()); overrides.push(pastey::paste!(stringify!([<$name:upper>])).into());
} }
} }
)+)+ )+)+
@@ -231,7 +231,7 @@ macro_rules! make_config {
element.insert("default".into(), serde_json::to_value(def.$name).unwrap()); element.insert("default".into(), serde_json::to_value(def.$name).unwrap());
element.insert("type".into(), (_get_form_type(stringify!($ty))).into()); element.insert("type".into(), (_get_form_type(stringify!($ty))).into());
element.insert("doc".into(), (_get_doc(concat!($($doc),+))).into()); element.insert("doc".into(), (_get_doc(concat!($($doc),+))).into());
element.insert("overridden".into(), (overridden.contains(&paste::paste!(stringify!([<$name:upper>])).into())).into()); element.insert("overridden".into(), (overridden.contains(&pastey::paste!(stringify!([<$name:upper>])).into())).into());
element element
}), }),
)+ )+
@@ -484,7 +484,8 @@ make_config! {
disable_icon_download: bool, true, def, false; disable_icon_download: bool, true, def, false;
/// Allow new signups |> Controls whether new users can register. Users can be invited by the vaultwarden admin even if this is disabled /// Allow new signups |> Controls whether new users can register. Users can be invited by the vaultwarden admin even if this is disabled
signups_allowed: bool, true, def, true; signups_allowed: bool, true, def, true;
/// Require email verification on signups. This will prevent logins from succeeding until the address has been verified /// Require email verification on signups. On new client versions, this will require verification at signup time. On older clients,
/// this will prevent logins from succeeding until the address has been verified
signups_verify: bool, true, def, false; signups_verify: bool, true, def, false;
/// If signups require email verification, automatically re-send verification email if it hasn't been sent for a while (in seconds) /// If signups require email verification, automatically re-send verification email if it hasn't been sent for a while (in seconds)
signups_verify_resend_time: u64, true, def, 3_600; signups_verify_resend_time: u64, true, def, 3_600;
@@ -734,7 +735,7 @@ make_config! {
email_expiration_time: u64, true, def, 600; email_expiration_time: u64, true, def, 600;
/// Maximum attempts |> Maximum attempts before an email token is reset and a new email will need to be sent /// Maximum attempts |> Maximum attempts before an email token is reset and a new email will need to be sent
email_attempts_limit: u64, true, def, 3; email_attempts_limit: u64, true, def, 3;
/// Automatically enforce at login |> Setup email 2FA provider regardless of any organization policy /// Setup email 2FA at signup |> Setup email 2FA provider on registration regardless of any organization policy
email_2fa_enforce_on_verified_invite: bool, true, def, false; email_2fa_enforce_on_verified_invite: bool, true, def, false;
/// Auto-enable 2FA (Know the risks!) |> Automatically setup email 2FA as fallback provider when needed /// Auto-enable 2FA (Know the risks!) |> Automatically setup email 2FA as fallback provider when needed
email_2fa_auto_fallback: bool, true, def, false; email_2fa_auto_fallback: bool, true, def, false;
@@ -842,6 +843,10 @@ fn validate_config(cfg: &ConfigItems) -> Result<(), Error> {
"inline-menu-positioning-improvements", "inline-menu-positioning-improvements",
"ssh-key-vault-item", "ssh-key-vault-item",
"ssh-agent", "ssh-agent",
"anon-addy-self-host-alias",
"simple-login-self-host-alias",
"mutual-tls",
"export-attachments",
]; ];
let configured_flags = parse_experimental_client_feature_flags(&cfg.experimental_client_feature_flags); 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: Vec<_> = configured_flags.keys().filter(|flag| !KNOWN_FLAGS.contains(&flag.as_str())).collect();
@@ -1383,6 +1388,7 @@ where
reg!("email/protected_action", ".html"); reg!("email/protected_action", ".html");
reg!("email/pw_hint_none", ".html"); reg!("email/pw_hint_none", ".html");
reg!("email/pw_hint_some", ".html"); reg!("email/pw_hint_some", ".html");
reg!("email/register_verify_email", ".html");
reg!("email/send_2fa_removed_from_org", ".html"); reg!("email/send_2fa_removed_from_org", ".html");
reg!("email/send_emergency_access_invite", ".html"); reg!("email/send_emergency_access_invite", ".html");
reg!("email/send_org_invite", ".html"); reg!("email/send_org_invite", ".html");
+2 -3
View File
@@ -110,7 +110,6 @@ pub fn generate_api_key() -> String {
// Constant time compare // Constant time compare
// //
pub fn ct_eq<T: AsRef<[u8]>, U: AsRef<[u8]>>(a: T, b: U) -> bool { pub fn ct_eq<T: AsRef<[u8]>, U: AsRef<[u8]>>(a: T, b: U) -> bool {
use ring::constant_time::verify_slices_are_equal; use subtle::ConstantTimeEq;
a.as_ref().ct_eq(b.as_ref()).into()
verify_slices_are_equal(a.as_ref(), b.as_ref()).is_ok()
} }
+4 -4
View File
@@ -130,7 +130,7 @@ macro_rules! generate_connections {
DbConnType::$name => { DbConnType::$name => {
#[cfg($name)] #[cfg($name)]
{ {
paste::paste!{ [< $name _migrations >]::run_migrations()?; } pastey::paste!{ [< $name _migrations >]::run_migrations()?; }
let manager = ConnectionManager::new(&url); let manager = ConnectionManager::new(&url);
let pool = Pool::builder() let pool = Pool::builder()
.max_size(CONFIG.database_max_conns()) .max_size(CONFIG.database_max_conns())
@@ -259,7 +259,7 @@ macro_rules! db_run {
$($( $($(
#[cfg($db)] #[cfg($db)]
$crate::db::DbConnInner::$db($conn) => { $crate::db::DbConnInner::$db($conn) => {
paste::paste! { pastey::paste! {
#[allow(unused)] use $crate::db::[<__ $db _schema>]::{self as schema, *}; #[allow(unused)] use $crate::db::[<__ $db _schema>]::{self as schema, *};
#[allow(unused)] use [<__ $db _model>]::*; #[allow(unused)] use [<__ $db _model>]::*;
} }
@@ -280,7 +280,7 @@ macro_rules! db_run {
$($( $($(
#[cfg($db)] #[cfg($db)]
$crate::db::DbConnInner::$db($conn) => { $crate::db::DbConnInner::$db($conn) => {
paste::paste! { pastey::paste! {
#[allow(unused)] use $crate::db::[<__ $db _schema>]::{self as schema, *}; #[allow(unused)] use $crate::db::[<__ $db _schema>]::{self as schema, *};
// @ RAW: #[allow(unused)] use [<__ $db _model>]::*; // @ RAW: #[allow(unused)] use [<__ $db _model>]::*;
} }
@@ -337,7 +337,7 @@ macro_rules! db_object {
}; };
( @db $db:ident | $( #[$attr:meta] )* | $name:ident | $( $( #[$field_attr:meta] )* $vis:vis $field:ident : $typ:ty),+) => { ( @db $db:ident | $( #[$attr:meta] )* | $name:ident | $( $( #[$field_attr:meta] )* $vis:vis $field:ident : $typ:ty),+) => {
paste::paste! { pastey::paste! {
#[allow(unused)] use super::*; #[allow(unused)] use super::*;
#[allow(unused)] use diesel::prelude::*; #[allow(unused)] use diesel::prelude::*;
#[allow(unused)] use $crate::db::[<__ $db _schema>]::*; #[allow(unused)] use $crate::db::[<__ $db _schema>]::*;
+1
View File
@@ -11,6 +11,7 @@ use macros::UuidFromParam;
db_object! { db_object! {
#[derive(Identifiable, Queryable, Insertable, AsChangeset)] #[derive(Identifiable, Queryable, Insertable, AsChangeset)]
#[diesel(table_name = collections)] #[diesel(table_name = collections)]
#[diesel(treat_none_as_null = true)]
#[diesel(primary_key(uuid))] #[diesel(primary_key(uuid))]
pub struct Collection { pub struct Collection {
pub uuid: CollectionId, pub uuid: CollectionId,
+4
View File
@@ -135,6 +135,10 @@ impl Device {
pub fn is_registered(&self) -> bool { pub fn is_registered(&self) -> bool {
self.push_uuid.is_some() self.push_uuid.is_some()
} }
pub fn is_cli(&self) -> bool {
matches!(DeviceType::from_i32(self.atype), DeviceType::WindowsCLI | DeviceType::MacOsCLI | DeviceType::LinuxCLI)
}
} }
pub struct DeviceWithAuthRequest { pub struct DeviceWithAuthRequest {
+1
View File
@@ -13,6 +13,7 @@ db_object! {
// Upstream SQL: https://github.com/bitwarden/server/blob/8a22c0479e987e756ce7412c48a732f9002f0a2d/src/Sql/dbo/Tables/Event.sql // Upstream SQL: https://github.com/bitwarden/server/blob/8a22c0479e987e756ce7412c48a732f9002f0a2d/src/Sql/dbo/Tables/Event.sql
#[derive(Identifiable, Queryable, Insertable, AsChangeset)] #[derive(Identifiable, Queryable, Insertable, AsChangeset)]
#[diesel(table_name = event)] #[diesel(table_name = event)]
#[diesel(treat_none_as_null = true)]
#[diesel(primary_key(uuid))] #[diesel(primary_key(uuid))]
pub struct Event { pub struct Event {
pub uuid: EventId, pub uuid: EventId,
+1
View File
@@ -10,6 +10,7 @@ use serde_json::Value;
db_object! { db_object! {
#[derive(Identifiable, Queryable, Insertable, AsChangeset)] #[derive(Identifiable, Queryable, Insertable, AsChangeset)]
#[diesel(table_name = groups)] #[diesel(table_name = groups)]
#[diesel(treat_none_as_null = true)]
#[diesel(primary_key(uuid))] #[diesel(primary_key(uuid))]
pub struct Group { pub struct Group {
pub uuid: GroupId, pub uuid: GroupId,
+5 -1
View File
@@ -21,7 +21,7 @@ db_object! {
} }
} }
// https://github.com/bitwarden/server/blob/b86a04cef9f1e1b82cf18e49fc94e017c641130c/src/Core/Enums/PolicyType.cs // https://github.com/bitwarden/server/blob/abfdf6f5cb0f1f1504dbaaaa0e04ce9cb60faf19/src/Core/AdminConsole/Enums/PolicyType.cs
#[derive(Copy, Clone, Eq, PartialEq, num_derive::FromPrimitive)] #[derive(Copy, Clone, Eq, PartialEq, num_derive::FromPrimitive)]
pub enum OrgPolicyType { pub enum OrgPolicyType {
TwoFactorAuthentication = 0, TwoFactorAuthentication = 0,
@@ -35,6 +35,10 @@ pub enum OrgPolicyType {
ResetPassword = 8, ResetPassword = 8,
// MaximumVaultTimeout = 9, // Not supported (Not AGPLv3 Licensed) // MaximumVaultTimeout = 9, // Not supported (Not AGPLv3 Licensed)
// DisablePersonalVaultExport = 10, // Not supported (Not AGPLv3 Licensed) // DisablePersonalVaultExport = 10, // Not supported (Not AGPLv3 Licensed)
// ActivateAutofill = 11,
// AutomaticAppLogIn = 12,
// FreeFamiliesSponsorshipPolicy = 13,
RemoveUnlockWithPin = 14,
} }
// https://github.com/bitwarden/server/blob/5cbdee137921a19b1f722920f0fa3cd45af2ef0f/src/Core/Models/Data/Organizations/Policies/SendOptionsPolicyData.cs // https://github.com/bitwarden/server/blob/5cbdee137921a19b1f722920f0fa3cd45af2ef0f/src/Core/Models/Data/Organizations/Policies/SendOptionsPolicyData.cs
+2
View File
@@ -17,6 +17,7 @@ use macros::UuidFromParam;
db_object! { db_object! {
#[derive(Identifiable, Queryable, Insertable, AsChangeset)] #[derive(Identifiable, Queryable, Insertable, AsChangeset)]
#[diesel(table_name = organizations)] #[diesel(table_name = organizations)]
#[diesel(treat_none_as_null = true)]
#[diesel(primary_key(uuid))] #[diesel(primary_key(uuid))]
pub struct Organization { pub struct Organization {
pub uuid: OrganizationId, pub uuid: OrganizationId,
@@ -28,6 +29,7 @@ db_object! {
#[derive(Identifiable, Queryable, Insertable, AsChangeset)] #[derive(Identifiable, Queryable, Insertable, AsChangeset)]
#[diesel(table_name = users_organizations)] #[diesel(table_name = users_organizations)]
#[diesel(treat_none_as_null = true)]
#[diesel(primary_key(uuid))] #[diesel(primary_key(uuid))]
pub struct Membership { pub struct Membership {
pub uuid: MembershipId, pub uuid: MembershipId,
+4 -4
View File
@@ -173,8 +173,8 @@ impl User {
/// * `password` - A str which contains a hashed version of the users master password. /// * `password` - A str which contains a hashed version of the users master password.
/// * `new_key` - A String which contains the new aKey value of the users master password. /// * `new_key` - A String which contains the new aKey value of the users master password.
/// * `allow_next_route` - A Option<Vec<String>> with the function names of the next allowed (rocket) routes. /// * `allow_next_route` - A Option<Vec<String>> with the function names of the next allowed (rocket) routes.
/// These routes are able to use the previous stamp id for the next 2 minutes. /// These routes are able to use the previous stamp id for the next 2 minutes.
/// After these 2 minutes this stamp will expire. /// After these 2 minutes this stamp will expire.
/// ///
pub fn set_password( pub fn set_password(
&mut self, &mut self,
@@ -206,8 +206,8 @@ impl User {
/// ///
/// # Arguments /// # Arguments
/// * `route_exception` - A Vec<String> with the function names of the next allowed (rocket) routes. /// * `route_exception` - A Vec<String> with the function names of the next allowed (rocket) routes.
/// These routes are able to use the previous stamp id for the next 2 minutes. /// These routes are able to use the previous stamp id for the next 2 minutes.
/// After these 2 minutes this stamp will expire. /// After these 2 minutes this stamp will expire.
/// ///
pub fn set_stamp_exception(&mut self, route_exception: Vec<String>) { pub fn set_stamp_exception(&mut self, route_exception: Vec<String>) {
let stamp_exception = UserStampException { let stamp_exception = UserStampException {
+5 -5
View File
@@ -6,7 +6,7 @@ use std::{
time::Duration, time::Duration,
}; };
use hickory_resolver::{system_conf::read_system_conf, TokioAsyncResolver}; use hickory_resolver::{name_server::TokioConnectionProvider, TokioResolver};
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use regex::Regex; use regex::Regex;
use reqwest::{ use reqwest::{
@@ -173,7 +173,7 @@ impl std::error::Error for CustomHttpClientError {}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
enum CustomDnsResolver { enum CustomDnsResolver {
Default(), Default(),
Hickory(Arc<TokioAsyncResolver>), Hickory(Arc<TokioResolver>),
} }
type BoxError = Box<dyn std::error::Error + Send + Sync>; type BoxError = Box<dyn std::error::Error + Send + Sync>;
@@ -184,9 +184,9 @@ impl CustomDnsResolver {
} }
fn new() -> Arc<Self> { fn new() -> Arc<Self> {
match read_system_conf() { match TokioResolver::builder(TokioConnectionProvider::default()) {
Ok((config, opts)) => { Ok(builder) => {
let resolver = TokioAsyncResolver::tokio(config.clone(), opts.clone()); let resolver = builder.build();
Arc::new(Self::Hickory(Arc::new(resolver))) Arc::new(Self::Hickory(Arc::new(resolver)))
} }
Err(e) => { Err(e) => {
+21
View File
@@ -201,6 +201,27 @@ pub async fn send_verify_email(address: &str, user_id: &UserId) -> EmptyResult {
send_email(address, &subject, body_html, body_text).await send_email(address, &subject, body_html, body_text).await
} }
pub async fn send_register_verify_email(email: &str, token: &str) -> EmptyResult {
let mut query = url::Url::parse("https://query.builder").unwrap();
query.query_pairs_mut().append_pair("email", email).append_pair("token", token);
let query_string = match query.query() {
None => err!("Failed to build verify URL query parameters"),
Some(query) => query,
};
let (subject, body_html, body_text) = get_text(
"email/register_verify_email",
json!({
// `url.Url` would place the anchor `#` after the query parameters
"url": format!("{}/#/finish-signup/?{}", CONFIG.domain(), query_string),
"img_src": CONFIG._smtp_img_src(),
"email": email,
}),
)?;
send_email(email, &subject, body_html, body_text).await
}
pub async fn send_welcome(address: &str) -> EmptyResult { pub async fn send_welcome(address: &str) -> EmptyResult {
let (subject, body_html, body_text) = get_text( let (subject, body_html, body_text) = get_text(
"email/welcome", "email/welcome",
+12 -15
View File
@@ -1,6 +1,6 @@
/*! /*!
* Bootstrap v5.3.3 (https://getbootstrap.com/) * Bootstrap v5.3.4 (https://getbootstrap.com/)
* Copyright 2011-2024 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) * Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/ */
(function (global, factory) { (function (global, factory) {
@@ -205,7 +205,7 @@
* @param {HTMLElement} element * @param {HTMLElement} element
* @return void * @return void
* *
* @see https://www.charistheo.io/blog/2021/02/restart-a-css-animation-with-javascript/#restarting-a-css-animation * @see https://www.harrytheo.com/blog/2021/02/restart-a-css-animation-with-javascript/#restarting-a-css-animation
*/ */
const reflow = element => { const reflow = element => {
element.offsetHeight; // eslint-disable-line no-unused-expressions element.offsetHeight; // eslint-disable-line no-unused-expressions
@@ -250,7 +250,7 @@
}); });
}; };
const execute = (possibleCallback, args = [], defaultValue = possibleCallback) => { const execute = (possibleCallback, args = [], defaultValue = possibleCallback) => {
return typeof possibleCallback === 'function' ? possibleCallback(...args) : defaultValue; return typeof possibleCallback === 'function' ? possibleCallback.call(...args) : defaultValue;
}; };
const executeAfterTransition = (callback, transitionElement, waitForTransition = true) => { const executeAfterTransition = (callback, transitionElement, waitForTransition = true) => {
if (!waitForTransition) { if (!waitForTransition) {
@@ -572,7 +572,7 @@
const bsKeys = Object.keys(element.dataset).filter(key => key.startsWith('bs') && !key.startsWith('bsConfig')); const bsKeys = Object.keys(element.dataset).filter(key => key.startsWith('bs') && !key.startsWith('bsConfig'));
for (const key of bsKeys) { for (const key of bsKeys) {
let pureKey = key.replace(/^bs/, ''); let pureKey = key.replace(/^bs/, '');
pureKey = pureKey.charAt(0).toLowerCase() + pureKey.slice(1, pureKey.length); pureKey = pureKey.charAt(0).toLowerCase() + pureKey.slice(1);
attributes[pureKey] = normalizeData(element.dataset[key]); attributes[pureKey] = normalizeData(element.dataset[key]);
} }
return attributes; return attributes;
@@ -647,7 +647,7 @@
* Constants * Constants
*/ */
const VERSION = '5.3.3'; const VERSION = '5.3.4';
/** /**
* Class definition * Class definition
@@ -2666,7 +2666,6 @@
var popperOffsets = computeOffsets({ var popperOffsets = computeOffsets({
reference: referenceClientRect, reference: referenceClientRect,
element: popperRect, element: popperRect,
strategy: 'absolute',
placement: placement placement: placement
}); });
var popperClientRect = rectToClientRect(Object.assign({}, popperRect, popperOffsets)); var popperClientRect = rectToClientRect(Object.assign({}, popperRect, popperOffsets));
@@ -2994,7 +2993,6 @@
state.modifiersData[name] = computeOffsets({ state.modifiersData[name] = computeOffsets({
reference: state.rects.reference, reference: state.rects.reference,
element: state.rects.popper, element: state.rects.popper,
strategy: 'absolute',
placement: state.placement placement: state.placement
}); });
} // eslint-disable-next-line import/no-unused-modules } // eslint-disable-next-line import/no-unused-modules
@@ -3701,7 +3699,7 @@
} }
_createPopper() { _createPopper() {
if (typeof Popper === 'undefined') { if (typeof Popper === 'undefined') {
throw new TypeError('Bootstrap\'s dropdowns require Popper (https://popper.js.org)'); throw new TypeError('Bootstrap\'s dropdowns require Popper (https://popper.js.org/docs/v2/)');
} }
let referenceElement = this._element; let referenceElement = this._element;
if (this._config.reference === 'parent') { if (this._config.reference === 'parent') {
@@ -3780,7 +3778,7 @@
} }
return { return {
...defaultBsPopperConfig, ...defaultBsPopperConfig,
...execute(this._config.popperConfig, [defaultBsPopperConfig]) ...execute(this._config.popperConfig, [undefined, defaultBsPopperConfig])
}; };
} }
_selectMenuItem({ _selectMenuItem({
@@ -4967,7 +4965,7 @@
return this._config.sanitize ? sanitizeHtml(arg, this._config.allowList, this._config.sanitizeFn) : arg; return this._config.sanitize ? sanitizeHtml(arg, this._config.allowList, this._config.sanitizeFn) : arg;
} }
_resolvePossibleFunction(arg) { _resolvePossibleFunction(arg) {
return execute(arg, [this]); return execute(arg, [undefined, this]);
} }
_putElementInTemplate(element, templateElement) { _putElementInTemplate(element, templateElement) {
if (this._config.html) { if (this._config.html) {
@@ -5066,7 +5064,7 @@
class Tooltip extends BaseComponent { class Tooltip extends BaseComponent {
constructor(element, config) { constructor(element, config) {
if (typeof Popper === 'undefined') { if (typeof Popper === 'undefined') {
throw new TypeError('Bootstrap\'s tooltips require Popper (https://popper.js.org)'); throw new TypeError('Bootstrap\'s tooltips require Popper (https://popper.js.org/docs/v2/)');
} }
super(element, config); super(element, config);
@@ -5112,7 +5110,6 @@
if (!this._isEnabled) { if (!this._isEnabled) {
return; return;
} }
this._activeTrigger.click = !this._activeTrigger.click;
if (this._isShown()) { if (this._isShown()) {
this._leave(); this._leave();
return; return;
@@ -5300,7 +5297,7 @@
return offset; return offset;
} }
_resolvePossibleFunction(arg) { _resolvePossibleFunction(arg) {
return execute(arg, [this._element]); return execute(arg, [this._element, this._element]);
} }
_getPopperConfig(attachment) { _getPopperConfig(attachment) {
const defaultBsPopperConfig = { const defaultBsPopperConfig = {
@@ -5338,7 +5335,7 @@
}; };
return { return {
...defaultBsPopperConfig, ...defaultBsPopperConfig,
...execute(this._config.popperConfig, [defaultBsPopperConfig]) ...execute(this._config.popperConfig, [undefined, defaultBsPopperConfig])
}; };
} }
_setListeners() { _setListeners() {
+116 -111
View File
@@ -1,7 +1,7 @@
@charset "UTF-8"; @charset "UTF-8";
/*! /*!
* Bootstrap v5.3.3 (https://getbootstrap.com/) * Bootstrap v5.3.4 (https://getbootstrap.com/)
* Copyright 2011-2024 The Bootstrap Authors * Copyright 2011-2025 The Bootstrap Authors
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/ */
:root, :root,
@@ -517,8 +517,8 @@ legend {
width: 100%; width: 100%;
padding: 0; padding: 0;
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
font-size: calc(1.275rem + 0.3vw);
line-height: inherit; line-height: inherit;
font-size: calc(1.275rem + 0.3vw);
} }
@media (min-width: 1200px) { @media (min-width: 1200px) {
legend { legend {
@@ -601,9 +601,9 @@ progress {
} }
.display-1 { .display-1 {
font-size: calc(1.625rem + 4.5vw);
font-weight: 300; font-weight: 300;
line-height: 1.2; line-height: 1.2;
font-size: calc(1.625rem + 4.5vw);
} }
@media (min-width: 1200px) { @media (min-width: 1200px) {
.display-1 { .display-1 {
@@ -612,9 +612,9 @@ progress {
} }
.display-2 { .display-2 {
font-size: calc(1.575rem + 3.9vw);
font-weight: 300; font-weight: 300;
line-height: 1.2; line-height: 1.2;
font-size: calc(1.575rem + 3.9vw);
} }
@media (min-width: 1200px) { @media (min-width: 1200px) {
.display-2 { .display-2 {
@@ -623,9 +623,9 @@ progress {
} }
.display-3 { .display-3 {
font-size: calc(1.525rem + 3.3vw);
font-weight: 300; font-weight: 300;
line-height: 1.2; line-height: 1.2;
font-size: calc(1.525rem + 3.3vw);
} }
@media (min-width: 1200px) { @media (min-width: 1200px) {
.display-3 { .display-3 {
@@ -634,9 +634,9 @@ progress {
} }
.display-4 { .display-4 {
font-size: calc(1.475rem + 2.7vw);
font-weight: 300; font-weight: 300;
line-height: 1.2; line-height: 1.2;
font-size: calc(1.475rem + 2.7vw);
} }
@media (min-width: 1200px) { @media (min-width: 1200px) {
.display-4 { .display-4 {
@@ -645,9 +645,9 @@ progress {
} }
.display-5 { .display-5 {
font-size: calc(1.425rem + 2.1vw);
font-weight: 300; font-weight: 300;
line-height: 1.2; line-height: 1.2;
font-size: calc(1.425rem + 2.1vw);
} }
@media (min-width: 1200px) { @media (min-width: 1200px) {
.display-5 { .display-5 {
@@ -656,9 +656,9 @@ progress {
} }
.display-6 { .display-6 {
font-size: calc(1.375rem + 1.5vw);
font-weight: 300; font-weight: 300;
line-height: 1.2; line-height: 1.2;
font-size: calc(1.375rem + 1.5vw);
} }
@media (min-width: 1200px) { @media (min-width: 1200px) {
.display-6 { .display-6 {
@@ -803,7 +803,7 @@ progress {
} }
.col { .col {
flex: 1 0 0%; flex: 1 0 0;
} }
.row-cols-auto > * { .row-cols-auto > * {
@@ -1012,7 +1012,7 @@ progress {
@media (min-width: 576px) { @media (min-width: 576px) {
.col-sm { .col-sm {
flex: 1 0 0%; flex: 1 0 0;
} }
.row-cols-sm-auto > * { .row-cols-sm-auto > * {
flex: 0 0 auto; flex: 0 0 auto;
@@ -1181,7 +1181,7 @@ progress {
} }
@media (min-width: 768px) { @media (min-width: 768px) {
.col-md { .col-md {
flex: 1 0 0%; flex: 1 0 0;
} }
.row-cols-md-auto > * { .row-cols-md-auto > * {
flex: 0 0 auto; flex: 0 0 auto;
@@ -1350,7 +1350,7 @@ progress {
} }
@media (min-width: 992px) { @media (min-width: 992px) {
.col-lg { .col-lg {
flex: 1 0 0%; flex: 1 0 0;
} }
.row-cols-lg-auto > * { .row-cols-lg-auto > * {
flex: 0 0 auto; flex: 0 0 auto;
@@ -1519,7 +1519,7 @@ progress {
} }
@media (min-width: 1200px) { @media (min-width: 1200px) {
.col-xl { .col-xl {
flex: 1 0 0%; flex: 1 0 0;
} }
.row-cols-xl-auto > * { .row-cols-xl-auto > * {
flex: 0 0 auto; flex: 0 0 auto;
@@ -1688,7 +1688,7 @@ progress {
} }
@media (min-width: 1400px) { @media (min-width: 1400px) {
.col-xxl { .col-xxl {
flex: 1 0 0%; flex: 1 0 0;
} }
.row-cols-xxl-auto > * { .row-cols-xxl-auto > * {
flex: 0 0 auto; flex: 0 0 auto;
@@ -2607,9 +2607,11 @@ textarea.form-control-lg {
top: 0; top: 0;
left: 0; left: 0;
z-index: 2; z-index: 2;
max-width: 100%;
height: 100%; height: 100%;
padding: 1rem 0.75rem; padding: 1rem 0.75rem;
overflow: hidden; overflow: hidden;
color: rgba(var(--bs-body-color-rgb), 0.65);
text-align: start; text-align: start;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
@@ -2634,7 +2636,7 @@ textarea.form-control-lg {
.form-floating > .form-control-plaintext::placeholder { .form-floating > .form-control-plaintext::placeholder {
color: transparent; color: transparent;
} }
.form-floating > .form-control:not(:-moz-placeholder-shown), .form-floating > .form-control-plaintext:not(:-moz-placeholder-shown) { .form-floating > .form-control:not(:-moz-placeholder), .form-floating > .form-control-plaintext:not(:-moz-placeholder) {
padding-top: 1.625rem; padding-top: 1.625rem;
padding-bottom: 0.625rem; padding-bottom: 0.625rem;
} }
@@ -2652,43 +2654,42 @@ textarea.form-control-lg {
.form-floating > .form-select { .form-floating > .form-select {
padding-top: 1.625rem; padding-top: 1.625rem;
padding-bottom: 0.625rem; padding-bottom: 0.625rem;
padding-left: 0.75rem;
} }
.form-floating > .form-control:not(:-moz-placeholder-shown) ~ label { .form-floating > .form-control:not(:-moz-placeholder) ~ label {
color: rgba(var(--bs-body-color-rgb), 0.65);
transform: scale(0.85) translateY(-0.5rem) translateX(0.15rem); transform: scale(0.85) translateY(-0.5rem) translateX(0.15rem);
} }
.form-floating > .form-control:focus ~ label, .form-floating > .form-control:focus ~ label,
.form-floating > .form-control:not(:placeholder-shown) ~ label, .form-floating > .form-control:not(:placeholder-shown) ~ label,
.form-floating > .form-control-plaintext ~ label, .form-floating > .form-control-plaintext ~ label,
.form-floating > .form-select ~ label { .form-floating > .form-select ~ label {
color: rgba(var(--bs-body-color-rgb), 0.65);
transform: scale(0.85) translateY(-0.5rem) translateX(0.15rem); transform: scale(0.85) translateY(-0.5rem) translateX(0.15rem);
} }
.form-floating > .form-control:not(:-moz-placeholder-shown) ~ label::after {
position: absolute;
inset: 1rem 0.375rem;
z-index: -1;
height: 1.5em;
content: "";
background-color: var(--bs-body-bg);
border-radius: var(--bs-border-radius);
}
.form-floating > .form-control:focus ~ label::after,
.form-floating > .form-control:not(:placeholder-shown) ~ label::after,
.form-floating > .form-control-plaintext ~ label::after,
.form-floating > .form-select ~ label::after {
position: absolute;
inset: 1rem 0.375rem;
z-index: -1;
height: 1.5em;
content: "";
background-color: var(--bs-body-bg);
border-radius: var(--bs-border-radius);
}
.form-floating > .form-control:-webkit-autofill ~ label { .form-floating > .form-control:-webkit-autofill ~ label {
color: rgba(var(--bs-body-color-rgb), 0.65);
transform: scale(0.85) translateY(-0.5rem) translateX(0.15rem); transform: scale(0.85) translateY(-0.5rem) translateX(0.15rem);
} }
.form-floating > textarea:not(:-moz-placeholder) ~ label::after {
position: absolute;
inset: 1rem 0.375rem;
z-index: -1;
height: 1.5em;
content: "";
background-color: var(--bs-body-bg);
border-radius: var(--bs-border-radius);
}
.form-floating > textarea:focus ~ label::after,
.form-floating > textarea:not(:placeholder-shown) ~ label::after {
position: absolute;
inset: 1rem 0.375rem;
z-index: -1;
height: 1.5em;
content: "";
background-color: var(--bs-body-bg);
border-radius: var(--bs-border-radius);
}
.form-floating > textarea:disabled ~ label::after {
background-color: var(--bs-secondary-bg);
}
.form-floating > .form-control-plaintext ~ label { .form-floating > .form-control-plaintext ~ label {
border-width: var(--bs-border-width) 0; border-width: var(--bs-border-width) 0;
} }
@@ -2696,10 +2697,6 @@ textarea.form-control-lg {
.form-floating > .form-control:disabled ~ label { .form-floating > .form-control:disabled ~ label {
color: #6c757d; color: #6c757d;
} }
.form-floating > :disabled ~ label::after,
.form-floating > .form-control:disabled ~ label::after {
background-color: var(--bs-secondary-bg);
}
.input-group { .input-group {
position: relative; position: relative;
@@ -2782,7 +2779,7 @@ textarea.form-control-lg {
border-bottom-right-radius: 0; border-bottom-right-radius: 0;
} }
.input-group > :not(:first-child):not(.dropdown-menu):not(.valid-tooltip):not(.valid-feedback):not(.invalid-tooltip):not(.invalid-feedback) { .input-group > :not(:first-child):not(.dropdown-menu):not(.valid-tooltip):not(.valid-feedback):not(.invalid-tooltip):not(.invalid-feedback) {
margin-left: calc(var(--bs-border-width) * -1); margin-left: calc(-1 * var(--bs-border-width));
border-top-left-radius: 0; border-top-left-radius: 0;
border-bottom-left-radius: 0; border-bottom-left-radius: 0;
} }
@@ -2824,7 +2821,7 @@ textarea.form-control-lg {
.was-validated .form-control:valid, .form-control.is-valid { .was-validated .form-control:valid, .form-control.is-valid {
border-color: var(--bs-form-valid-border-color); border-color: var(--bs-form-valid-border-color);
padding-right: calc(1.5em + 0.75rem); padding-right: calc(1.5em + 0.75rem);
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e"); background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1'/%3e%3c/svg%3e");
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: right calc(0.375em + 0.1875rem) center; background-position: right calc(0.375em + 0.1875rem) center;
background-size: calc(0.75em + 0.375rem) calc(0.75em + 0.375rem); background-size: calc(0.75em + 0.375rem) calc(0.75em + 0.375rem);
@@ -2843,7 +2840,7 @@ textarea.form-control-lg {
border-color: var(--bs-form-valid-border-color); border-color: var(--bs-form-valid-border-color);
} }
.was-validated .form-select:valid:not([multiple]):not([size]), .was-validated .form-select:valid:not([multiple])[size="1"], .form-select.is-valid:not([multiple]):not([size]), .form-select.is-valid:not([multiple])[size="1"] { .was-validated .form-select:valid:not([multiple]):not([size]), .was-validated .form-select:valid:not([multiple])[size="1"], .form-select.is-valid:not([multiple]):not([size]), .form-select.is-valid:not([multiple])[size="1"] {
--bs-form-select-bg-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e"); --bs-form-select-bg-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1'/%3e%3c/svg%3e");
padding-right: 4.125rem; padding-right: 4.125rem;
background-position: right 0.75rem center, center right 2.25rem; background-position: right 0.75rem center, center right 2.25rem;
background-size: 16px 12px, calc(0.75em + 0.375rem) calc(0.75em + 0.375rem); background-size: 16px 12px, calc(0.75em + 0.375rem) calc(0.75em + 0.375rem);
@@ -3755,7 +3752,7 @@ textarea.form-control-lg {
} }
.btn-group > :not(.btn-check:first-child) + .btn, .btn-group > :not(.btn-check:first-child) + .btn,
.btn-group > .btn-group:not(:first-child) { .btn-group > .btn-group:not(:first-child) {
margin-left: calc(var(--bs-border-width) * -1); margin-left: calc(-1 * var(--bs-border-width));
} }
.btn-group > .btn:not(:last-child):not(.dropdown-toggle), .btn-group > .btn:not(:last-child):not(.dropdown-toggle),
.btn-group > .btn.dropdown-toggle-split:first-child, .btn-group > .btn.dropdown-toggle-split:first-child,
@@ -3802,14 +3799,15 @@ textarea.form-control-lg {
} }
.btn-group-vertical > .btn:not(:first-child), .btn-group-vertical > .btn:not(:first-child),
.btn-group-vertical > .btn-group:not(:first-child) { .btn-group-vertical > .btn-group:not(:first-child) {
margin-top: calc(var(--bs-border-width) * -1); margin-top: calc(-1 * var(--bs-border-width));
} }
.btn-group-vertical > .btn:not(:last-child):not(.dropdown-toggle), .btn-group-vertical > .btn:not(:last-child):not(.dropdown-toggle),
.btn-group-vertical > .btn-group:not(:last-child) > .btn { .btn-group-vertical > .btn-group:not(:last-child) > .btn {
border-bottom-right-radius: 0; border-bottom-right-radius: 0;
border-bottom-left-radius: 0; border-bottom-left-radius: 0;
} }
.btn-group-vertical > .btn ~ .btn, .btn-group-vertical > .btn:nth-child(n+3),
.btn-group-vertical > :not(.btn-check) + .btn,
.btn-group-vertical > .btn-group:not(:first-child) > .btn { .btn-group-vertical > .btn-group:not(:first-child) > .btn {
border-top-left-radius: 0; border-top-left-radius: 0;
border-top-right-radius: 0; border-top-right-radius: 0;
@@ -3933,8 +3931,8 @@ textarea.form-control-lg {
.nav-justified > .nav-link, .nav-justified > .nav-link,
.nav-justified .nav-item { .nav-justified .nav-item {
flex-basis: 0;
flex-grow: 1; flex-grow: 1;
flex-basis: 0;
text-align: center; text-align: center;
} }
@@ -4035,8 +4033,8 @@ textarea.form-control-lg {
} }
.navbar-collapse { .navbar-collapse {
flex-basis: 100%;
flex-grow: 1; flex-grow: 1;
flex-basis: 100%;
align-items: center; align-items: center;
} }
@@ -4531,7 +4529,7 @@ textarea.form-control-lg {
flex-flow: row wrap; flex-flow: row wrap;
} }
.card-group > .card { .card-group > .card {
flex: 1 0 0%; flex: 1 0 0;
margin-bottom: 0; margin-bottom: 0;
} }
.card-group > .card + .card { .card-group > .card + .card {
@@ -4576,11 +4574,11 @@ textarea.form-control-lg {
--bs-accordion-btn-padding-y: 1rem; --bs-accordion-btn-padding-y: 1rem;
--bs-accordion-btn-color: var(--bs-body-color); --bs-accordion-btn-color: var(--bs-body-color);
--bs-accordion-btn-bg: var(--bs-accordion-bg); --bs-accordion-btn-bg: var(--bs-accordion-bg);
--bs-accordion-btn-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23212529' stroke-linecap='round' stroke-linejoin='round'%3e%3cpath d='M2 5L8 11L14 5'/%3e%3c/svg%3e"); --bs-accordion-btn-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23212529' stroke-linecap='round' stroke-linejoin='round'%3e%3cpath d='m2 5 6 6 6-6'/%3e%3c/svg%3e");
--bs-accordion-btn-icon-width: 1.25rem; --bs-accordion-btn-icon-width: 1.25rem;
--bs-accordion-btn-icon-transform: rotate(-180deg); --bs-accordion-btn-icon-transform: rotate(-180deg);
--bs-accordion-btn-icon-transition: transform 0.2s ease-in-out; --bs-accordion-btn-icon-transition: transform 0.2s ease-in-out;
--bs-accordion-btn-active-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23052c65' stroke-linecap='round' stroke-linejoin='round'%3e%3cpath d='M2 5L8 11L14 5'/%3e%3c/svg%3e"); --bs-accordion-btn-active-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23052c65' stroke-linecap='round' stroke-linejoin='round'%3e%3cpath d='m2 5 6 6 6-6'/%3e%3c/svg%3e");
--bs-accordion-btn-focus-box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25); --bs-accordion-btn-focus-box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
--bs-accordion-body-padding-x: 1.25rem; --bs-accordion-body-padding-x: 1.25rem;
--bs-accordion-body-padding-y: 1rem; --bs-accordion-body-padding-y: 1rem;
@@ -4690,16 +4688,15 @@ textarea.form-control-lg {
.accordion-flush > .accordion-item:last-child { .accordion-flush > .accordion-item:last-child {
border-bottom: 0; border-bottom: 0;
} }
.accordion-flush > .accordion-item > .accordion-header .accordion-button, .accordion-flush > .accordion-item > .accordion-header .accordion-button.collapsed { .accordion-flush > .accordion-item > .accordion-collapse,
border-radius: 0; .accordion-flush > .accordion-item > .accordion-header .accordion-button,
} .accordion-flush > .accordion-item > .accordion-header .accordion-button.collapsed {
.accordion-flush > .accordion-item > .accordion-collapse {
border-radius: 0; border-radius: 0;
} }
[data-bs-theme=dark] .accordion-button::after { [data-bs-theme=dark] .accordion-button::after {
--bs-accordion-btn-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%236ea8fe'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e"); --bs-accordion-btn-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%236ea8fe'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708'/%3e%3c/svg%3e");
--bs-accordion-btn-active-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%236ea8fe'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e"); --bs-accordion-btn-active-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%236ea8fe'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708'/%3e%3c/svg%3e");
} }
.breadcrumb { .breadcrumb {
@@ -4803,7 +4800,7 @@ textarea.form-control-lg {
} }
.page-item:not(:first-child) .page-link { .page-item:not(:first-child) .page-link {
margin-left: calc(var(--bs-border-width) * -1); margin-left: calc(-1 * var(--bs-border-width));
} }
.page-item:first-child .page-link { .page-item:first-child .page-link {
border-top-left-radius: var(--bs-pagination-border-radius); border-top-left-radius: var(--bs-pagination-border-radius);
@@ -4952,7 +4949,7 @@ textarea.form-control-lg {
@keyframes progress-bar-stripes { @keyframes progress-bar-stripes {
0% { 0% {
background-position-x: 1rem; background-position-x: var(--bs-progress-height);
} }
} }
.progress, .progress,
@@ -5046,22 +5043,6 @@ textarea.form-control-lg {
counter-increment: section; counter-increment: section;
} }
.list-group-item-action {
width: 100%;
color: var(--bs-list-group-action-color);
text-align: inherit;
}
.list-group-item-action:hover, .list-group-item-action:focus {
z-index: 1;
color: var(--bs-list-group-action-hover-color);
text-decoration: none;
background-color: var(--bs-list-group-action-hover-bg);
}
.list-group-item-action:active {
color: var(--bs-list-group-action-active-color);
background-color: var(--bs-list-group-action-active-bg);
}
.list-group-item { .list-group-item {
position: relative; position: relative;
display: block; display: block;
@@ -5098,6 +5079,22 @@ textarea.form-control-lg {
border-top-width: var(--bs-list-group-border-width); border-top-width: var(--bs-list-group-border-width);
} }
.list-group-item-action {
width: 100%;
color: var(--bs-list-group-action-color);
text-align: inherit;
}
.list-group-item-action:not(.active):hover, .list-group-item-action:not(.active):focus {
z-index: 1;
color: var(--bs-list-group-action-hover-color);
text-decoration: none;
background-color: var(--bs-list-group-action-hover-bg);
}
.list-group-item-action:not(.active):active {
color: var(--bs-list-group-action-active-color);
background-color: var(--bs-list-group-action-active-bg);
}
.list-group-horizontal { .list-group-horizontal {
flex-direction: row; flex-direction: row;
} }
@@ -5357,19 +5354,19 @@ textarea.form-control-lg {
.btn-close { .btn-close {
--bs-btn-close-color: #000; --bs-btn-close-color: #000;
--bs-btn-close-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000'%3e%3cpath d='M.293.293a1 1 0 0 1 1.414 0L8 6.586 14.293.293a1 1 0 1 1 1.414 1.414L9.414 8l6.293 6.293a1 1 0 0 1-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 0 1-1.414-1.414L6.586 8 .293 1.707a1 1 0 0 1 0-1.414z'/%3e%3c/svg%3e"); --bs-btn-close-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000'%3e%3cpath d='M.293.293a1 1 0 0 1 1.414 0L8 6.586 14.293.293a1 1 0 1 1 1.414 1.414L9.414 8l6.293 6.293a1 1 0 0 1-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 0 1-1.414-1.414L6.586 8 .293 1.707a1 1 0 0 1 0-1.414'/%3e%3c/svg%3e");
--bs-btn-close-opacity: 0.5; --bs-btn-close-opacity: 0.5;
--bs-btn-close-hover-opacity: 0.75; --bs-btn-close-hover-opacity: 0.75;
--bs-btn-close-focus-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25); --bs-btn-close-focus-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
--bs-btn-close-focus-opacity: 1; --bs-btn-close-focus-opacity: 1;
--bs-btn-close-disabled-opacity: 0.25; --bs-btn-close-disabled-opacity: 0.25;
--bs-btn-close-white-filter: invert(1) grayscale(100%) brightness(200%);
box-sizing: content-box; box-sizing: content-box;
width: 1em; width: 1em;
height: 1em; height: 1em;
padding: 0.25em 0.25em; padding: 0.25em 0.25em;
color: var(--bs-btn-close-color); color: var(--bs-btn-close-color);
background: transparent var(--bs-btn-close-bg) center/1em auto no-repeat; background: transparent var(--bs-btn-close-bg) center/1em auto no-repeat;
filter: var(--bs-btn-close-filter);
border: 0; border: 0;
border-radius: 0.375rem; border-radius: 0.375rem;
opacity: var(--bs-btn-close-opacity); opacity: var(--bs-btn-close-opacity);
@@ -5393,11 +5390,16 @@ textarea.form-control-lg {
} }
.btn-close-white { .btn-close-white {
filter: var(--bs-btn-close-white-filter); --bs-btn-close-filter: invert(1) grayscale(100%) brightness(200%);
} }
[data-bs-theme=dark] .btn-close { :root,
filter: var(--bs-btn-close-white-filter); [data-bs-theme=light] {
--bs-btn-close-filter: ;
}
[data-bs-theme=dark] {
--bs-btn-close-filter: invert(1) grayscale(100%) brightness(200%);
} }
.toast { .toast {
@@ -5474,7 +5476,7 @@ textarea.form-control-lg {
--bs-modal-width: 500px; --bs-modal-width: 500px;
--bs-modal-padding: 1rem; --bs-modal-padding: 1rem;
--bs-modal-margin: 0.5rem; --bs-modal-margin: 0.5rem;
--bs-modal-color: ; --bs-modal-color: var(--bs-body-color);
--bs-modal-bg: var(--bs-body-bg); --bs-modal-bg: var(--bs-body-bg);
--bs-modal-border-color: var(--bs-border-color-translucent); --bs-modal-border-color: var(--bs-border-color-translucent);
--bs-modal-border-width: var(--bs-border-width); --bs-modal-border-width: var(--bs-border-width);
@@ -5510,8 +5512,8 @@ textarea.form-control-lg {
pointer-events: none; pointer-events: none;
} }
.modal.fade .modal-dialog { .modal.fade .modal-dialog {
transition: transform 0.3s ease-out;
transform: translate(0, -50px); transform: translate(0, -50px);
transition: transform 0.3s ease-out;
} }
@media (prefers-reduced-motion: reduce) { @media (prefers-reduced-motion: reduce) {
.modal.fade .modal-dialog { .modal.fade .modal-dialog {
@@ -5586,7 +5588,10 @@ textarea.form-control-lg {
} }
.modal-header .btn-close { .modal-header .btn-close {
padding: calc(var(--bs-modal-header-padding-y) * 0.5) calc(var(--bs-modal-header-padding-x) * 0.5); padding: calc(var(--bs-modal-header-padding-y) * 0.5) calc(var(--bs-modal-header-padding-x) * 0.5);
margin: calc(-0.5 * var(--bs-modal-header-padding-y)) calc(-0.5 * var(--bs-modal-header-padding-x)) calc(-0.5 * var(--bs-modal-header-padding-y)) auto; margin-top: calc(-0.5 * var(--bs-modal-header-padding-y));
margin-right: calc(-0.5 * var(--bs-modal-header-padding-x));
margin-bottom: calc(-0.5 * var(--bs-modal-header-padding-y));
margin-left: auto;
} }
.modal-title { .modal-title {
@@ -6107,6 +6112,7 @@ textarea.form-control-lg {
color: #fff; color: #fff;
text-align: center; text-align: center;
background: none; background: none;
filter: var(--bs-carousel-control-icon-filter);
border: 0; border: 0;
opacity: 0.5; opacity: 0.5;
transition: opacity 0.15s ease; transition: opacity 0.15s ease;
@@ -6145,11 +6151,11 @@ textarea.form-control-lg {
} }
.carousel-control-prev-icon { .carousel-control-prev-icon {
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e") /*rtl:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e")*/; background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0'/%3e%3c/svg%3e") /*rtl:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708'/%3e%3c/svg%3e")*/;
} }
.carousel-control-next-icon { .carousel-control-next-icon {
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e") /*rtl:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e")*/; background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708'/%3e%3c/svg%3e") /*rtl:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0'/%3e%3c/svg%3e")*/;
} }
.carousel-indicators { .carousel-indicators {
@@ -6175,7 +6181,7 @@ textarea.form-control-lg {
margin-left: 3px; margin-left: 3px;
text-indent: -999px; text-indent: -999px;
cursor: pointer; cursor: pointer;
background-color: #fff; background-color: var(--bs-carousel-indicator-active-bg);
background-clip: padding-box; background-clip: padding-box;
border: 0; border: 0;
border-top: 10px solid transparent; border-top: 10px solid transparent;
@@ -6199,31 +6205,27 @@ textarea.form-control-lg {
left: 15%; left: 15%;
padding-top: 1.25rem; padding-top: 1.25rem;
padding-bottom: 1.25rem; padding-bottom: 1.25rem;
color: #fff; color: var(--bs-carousel-caption-color);
text-align: center; text-align: center;
} }
.carousel-dark .carousel-control-prev-icon, .carousel-dark {
.carousel-dark .carousel-control-next-icon { --bs-carousel-indicator-active-bg: #000;
filter: invert(1) grayscale(100); --bs-carousel-caption-color: #000;
} --bs-carousel-control-icon-filter: invert(1) grayscale(100);
.carousel-dark .carousel-indicators [data-bs-target] {
background-color: #000;
}
.carousel-dark .carousel-caption {
color: #000;
} }
[data-bs-theme=dark] .carousel .carousel-control-prev-icon, :root,
[data-bs-theme=dark] .carousel .carousel-control-next-icon, [data-bs-theme=dark].carousel .carousel-control-prev-icon, [data-bs-theme=light] {
[data-bs-theme=dark].carousel .carousel-control-next-icon { --bs-carousel-indicator-active-bg: #fff;
filter: invert(1) grayscale(100); --bs-carousel-caption-color: #fff;
--bs-carousel-control-icon-filter: ;
} }
[data-bs-theme=dark] .carousel .carousel-indicators [data-bs-target], [data-bs-theme=dark].carousel .carousel-indicators [data-bs-target] {
background-color: #000; [data-bs-theme=dark] {
} --bs-carousel-indicator-active-bg: #000;
[data-bs-theme=dark] .carousel .carousel-caption, [data-bs-theme=dark].carousel .carousel-caption { --bs-carousel-caption-color: #000;
color: #000; --bs-carousel-control-icon-filter: invert(1) grayscale(100);
} }
.spinner-grow, .spinner-grow,
@@ -6773,7 +6775,10 @@ textarea.form-control-lg {
} }
.offcanvas-header .btn-close { .offcanvas-header .btn-close {
padding: calc(var(--bs-offcanvas-padding-y) * 0.5) calc(var(--bs-offcanvas-padding-x) * 0.5); padding: calc(var(--bs-offcanvas-padding-y) * 0.5) calc(var(--bs-offcanvas-padding-x) * 0.5);
margin: calc(-0.5 * var(--bs-offcanvas-padding-y)) calc(-0.5 * var(--bs-offcanvas-padding-x)) calc(-0.5 * var(--bs-offcanvas-padding-y)) auto; margin-top: calc(-0.5 * var(--bs-offcanvas-padding-y));
margin-right: calc(-0.5 * var(--bs-offcanvas-padding-x));
margin-bottom: calc(-0.5 * var(--bs-offcanvas-padding-y));
margin-left: auto;
} }
.offcanvas-title { .offcanvas-title {
+14 -12
View File
@@ -4,13 +4,12 @@
* *
* To rebuild or modify this file with the latest versions of the included * To rebuild or modify this file with the latest versions of the included
* software please visit: * software please visit:
* https://datatables.net/download/#bs5/dt-2.1.8 * https://datatables.net/download/#bs5/dt-2.2.2
* *
* Included libraries: * Included libraries:
* DataTables 2.1.8 * DataTables 2.2.2
*/ */
@charset "UTF-8";
:root { :root {
--dt-row-selected: 13, 110, 253; --dt-row-selected: 13, 110, 253;
--dt-row-selected-text: 255, 255, 255; --dt-row-selected-text: 255, 255, 255;
@@ -43,6 +42,9 @@ table.dataTable tr.dt-hasChild td.dt-control:before {
border-bottom: 0px solid transparent; border-bottom: 0px solid transparent;
border-right: 5px solid transparent; border-right: 5px solid transparent;
} }
table.dataTable tfoot:empty {
display: none;
}
html.dark table.dataTable td.dt-control:before, html.dark table.dataTable td.dt-control:before,
:root[data-bs-theme=dark] table.dataTable td.dt-control:before, :root[data-bs-theme=dark] table.dataTable td.dt-control:before,
@@ -90,8 +92,8 @@ table.dataTable thead > tr > td.dt-ordering-asc span.dt-column-order:before {
position: absolute; position: absolute;
display: block; display: block;
bottom: 50%; bottom: 50%;
content: ""; content: "\25B2";
content: ""/""; content: "\25B2"/"";
} }
table.dataTable thead > tr > th.dt-orderable-desc span.dt-column-order:after, table.dataTable thead > tr > th.dt-ordering-desc span.dt-column-order:after, table.dataTable thead > tr > th.dt-orderable-desc span.dt-column-order:after, table.dataTable thead > tr > th.dt-ordering-desc span.dt-column-order:after,
table.dataTable thead > tr > td.dt-orderable-desc span.dt-column-order:after, table.dataTable thead > tr > td.dt-orderable-desc span.dt-column-order:after,
@@ -99,8 +101,8 @@ table.dataTable thead > tr > td.dt-ordering-desc span.dt-column-order:after {
position: absolute; position: absolute;
display: block; display: block;
top: 50%; top: 50%;
content: ""; content: "\25BC";
content: ""/""; content: "\25BC"/"";
} }
table.dataTable thead > tr > th.dt-orderable-asc, table.dataTable thead > tr > th.dt-orderable-desc, table.dataTable thead > tr > th.dt-ordering-asc, table.dataTable thead > tr > th.dt-ordering-desc, table.dataTable thead > tr > th.dt-orderable-asc, table.dataTable thead > tr > th.dt-orderable-desc, table.dataTable thead > tr > th.dt-ordering-asc, table.dataTable thead > tr > th.dt-ordering-desc,
table.dataTable thead > tr > td.dt-orderable-asc, table.dataTable thead > tr > td.dt-orderable-asc,
@@ -251,6 +253,11 @@ table.dataTable th,
table.dataTable td { table.dataTable td {
box-sizing: border-box; box-sizing: border-box;
} }
table.dataTable th.dt-type-numeric, table.dataTable th.dt-type-date,
table.dataTable td.dt-type-numeric,
table.dataTable td.dt-type-date {
text-align: right;
}
table.dataTable th.dt-left, table.dataTable th.dt-left,
table.dataTable td.dt-left { table.dataTable td.dt-left {
text-align: left; text-align: left;
@@ -276,11 +283,6 @@ table.dataTable td.dt-empty {
text-align: center; text-align: center;
vertical-align: top; vertical-align: top;
} }
table.dataTable th.dt-type-numeric, table.dataTable th.dt-type-date,
table.dataTable td.dt-type-numeric,
table.dataTable td.dt-type-date {
text-align: right;
}
table.dataTable thead th, table.dataTable thead th,
table.dataTable thead td, table.dataTable thead td,
table.dataTable tfoot th, table.dataTable tfoot th,
+355 -148
View File
@@ -4,34 +4,16 @@
* *
* To rebuild or modify this file with the latest versions of the included * To rebuild or modify this file with the latest versions of the included
* software please visit: * software please visit:
* https://datatables.net/download/#bs5/dt-2.1.8 * https://datatables.net/download/#bs5/dt-2.2.2
* *
* Included libraries: * Included libraries:
* DataTables 2.1.8 * DataTables 2.2.2
*/ */
/*! DataTables 2.1.8 /*! DataTables 2.2.2
* © SpryMedia Ltd - datatables.net/license * © SpryMedia Ltd - datatables.net/license
*/ */
/**
* @summary DataTables
* @description Paginate, search and order HTML tables
* @version 2.1.8
* @author SpryMedia Ltd
* @contact www.datatables.net
* @copyright SpryMedia Ltd.
*
* This source file is free software, available under the following license:
* MIT license - https://datatables.net/license
*
* This source file is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details.
*
* For details please refer to: https://www.datatables.net
*/
(function( factory ) { (function( factory ) {
"use strict"; "use strict";
@@ -441,7 +423,6 @@
thead = $('<thead/>').appendTo($this); thead = $('<thead/>').appendTo($this);
} }
oSettings.nTHead = thead[0]; oSettings.nTHead = thead[0];
$('tr', thead).addClass(oClasses.thead.row);
var tbody = $this.children('tbody'); var tbody = $this.children('tbody');
if ( tbody.length === 0 ) { if ( tbody.length === 0 ) {
@@ -456,7 +437,6 @@
tfoot = $('<tfoot/>').appendTo($this); tfoot = $('<tfoot/>').appendTo($this);
} }
oSettings.nTFoot = tfoot[0]; oSettings.nTFoot = tfoot[0];
$('tr', tfoot).addClass(oClasses.tfoot.row);
// Copy the data index array // Copy the data index array
oSettings.aiDisplay = oSettings.aiDisplayMaster.slice(); oSettings.aiDisplay = oSettings.aiDisplayMaster.slice();
@@ -539,7 +519,7 @@
* *
* @type string * @type string
*/ */
builder: "bs5/dt-2.1.8", builder: "bs5/dt-2.2.2",
/** /**
@@ -2173,6 +2153,10 @@
var width = _fnColumnsSumWidth(settings, [i], false, false); var width = _fnColumnsSumWidth(settings, [i], false, false);
cols[i].colEl.css('width', width); cols[i].colEl.css('width', width);
if (settings.oScroll.sX) {
cols[i].colEl.css('min-width', width);
}
} }
} }
@@ -3240,9 +3224,13 @@
// Add the number of cells needed to make up to the number of columns // Add the number of cells needed to make up to the number of columns
if (row.length === 1) { if (row.length === 1) {
var cells = $('td, th', row); var cellCount = 0;
$('td, th', row).each(function () {
cellCount += this.colSpan;
});
for ( i=cells.length, ien=columns.length ; i<ien ; i++ ) { for ( i=cellCount, ien=columns.length ; i<ien ; i++ ) {
$('<th/>') $('<th/>')
.html( columns[i][titleProp] || '' ) .html( columns[i][titleProp] || '' )
.appendTo( row ); .appendTo( row );
@@ -3254,9 +3242,11 @@
if (side === 'header') { if (side === 'header') {
settings.aoHeader = detected; settings.aoHeader = detected;
$('tr', target).addClass(classes.thead.row);
} }
else { else {
settings.aoFooter = detected; settings.aoFooter = detected;
$('tr', target).addClass(classes.tfoot.row);
} }
// Every cell needs to be passed through the renderer // Every cell needs to be passed through the renderer
@@ -4519,7 +4509,7 @@
// So the array reference doesn't break set the results into the // So the array reference doesn't break set the results into the
// existing array // existing array
displayRows.length = 0; displayRows.length = 0;
displayRows.push.apply(displayRows, rows); _fnArrayApply(displayRows, rows);
} }
} }
@@ -5247,8 +5237,11 @@
// [].find, but it wasn't supported in Chrome until Sept 2015, and DT has 10 year // [].find, but it wasn't supported in Chrome until Sept 2015, and DT has 10 year
// browser support // browser support
var firstTr = null; var firstTr = null;
var start = _fnDataSource( settings ) !== 'ssp'
? settings._iDisplayStart
: 0;
for (i=settings._iDisplayStart ; i<settings.aiDisplay.length ; i++) { for (i=start ; i<start + settings.aiDisplay.length ; i++) {
var idx = settings.aiDisplay[i]; var idx = settings.aiDisplay[i];
var tr = settings.aoData[idx].nTr; var tr = settings.aoData[idx].nTr;
@@ -5263,7 +5256,7 @@
return { return {
idx: _fnVisibleToColumnIndex(settings, vis), idx: _fnVisibleToColumnIndex(settings, vis),
width: $(this).outerWidth() width: $(this).outerWidth()
} };
}); });
// Check against what the colgroup > col is set to and correct if needed // Check against what the colgroup > col is set to and correct if needed
@@ -5273,6 +5266,10 @@
if (colWidth !== colSizes[i].width) { if (colWidth !== colSizes[i].width) {
colEl.style.width = colSizes[i].width + 'px'; colEl.style.width = colSizes[i].width + 'px';
if (scroll.sX) {
colEl.style.minWidth = colSizes[i].width + 'px';
}
} }
} }
} }
@@ -5365,6 +5362,14 @@
i, column, columnIdx; i, column, columnIdx;
var styleWidth = table.style.width; var styleWidth = table.style.width;
var containerWidth = _fnWrapperWidth(settings);
// Don't re-run for the same width as the last time
if (containerWidth === settings.containerWidth) {
return false;
}
settings.containerWidth = containerWidth;
// If there is no width applied as a CSS style or as an attribute, we assume that // If there is no width applied as a CSS style or as an attribute, we assume that
// the width is intended to be 100%, which is usually is in CSS, but it is very // the width is intended to be 100%, which is usually is in CSS, but it is very
@@ -5422,6 +5427,8 @@
// browser will collapse it. If this width is smaller than the // browser will collapse it. If this width is smaller than the
// width the column requires, then it will have no effect // width the column requires, then it will have no effect
if ( scrollX ) { if ( scrollX ) {
this.style.minWidth = width;
$( this ).append( $('<div/>').css( { $( this ).append( $('<div/>').css( {
width: width, width: width,
margin: 0, margin: 0,
@@ -5490,15 +5497,15 @@
// If there is no width attribute or style, then allow the table to // If there is no width attribute or style, then allow the table to
// collapse // collapse
if ( tmpTable.width() < tableContainer.clientWidth && tableWidthAttr ) { if ( tmpTable.outerWidth() < tableContainer.clientWidth && tableWidthAttr ) {
tmpTable.width( tableContainer.clientWidth ); tmpTable.outerWidth( tableContainer.clientWidth );
} }
} }
else if ( scrollY ) { else if ( scrollY ) {
tmpTable.width( tableContainer.clientWidth ); tmpTable.outerWidth( tableContainer.clientWidth );
} }
else if ( tableWidthAttr ) { else if ( tableWidthAttr ) {
tmpTable.width( tableWidthAttr ); tmpTable.outerWidth( tableWidthAttr );
} }
// Get the width of each column in the constructed table // Get the width of each column in the constructed table
@@ -5531,20 +5538,64 @@
} }
if ( (tableWidthAttr || scrollX) && ! settings._reszEvt ) { if ( (tableWidthAttr || scrollX) && ! settings._reszEvt ) {
var bindResize = function () { var resize = DataTable.util.throttle( function () {
$(window).on('resize.DT-'+settings.sInstance, DataTable.util.throttle( function () { var newWidth = _fnWrapperWidth(settings);
if (! settings.bDestroying) {
_fnAdjustColumnSizing( settings );
}
} ) );
};
bindResize(); // Don't do it if destroying or the container width is 0
if (! settings.bDestroying && newWidth !== 0) {
_fnAdjustColumnSizing( settings );
}
} );
// For browsers that support it (~2020 onwards for wide support) we can watch for the
// container changing width.
if (window.ResizeObserver) {
// This is a tricky beast - if the element is visible when `.observe()` is called,
// then the callback is immediately run. Which we don't want. If the element isn't
// visible, then it isn't run, but we want it to run when it is then made visible.
// This flag allows the above to be satisfied.
var first = $(settings.nTableWrapper).is(':visible');
// Use an empty div to attach the observer so it isn't impacted by height changes
var resizer = $('<div>')
.css({
width: '100%',
height: 0
})
.addClass('dt-autosize')
.appendTo(settings.nTableWrapper);
settings.resizeObserver = new ResizeObserver(function (e) {
if (first) {
first = false;
}
else {
resize();
}
});
settings.resizeObserver.observe(resizer[0]);
}
else {
// For old browsers, the best we can do is listen for a window resize
$(window).on('resize.DT-'+settings.sInstance, resize);
}
settings._reszEvt = true; settings._reszEvt = true;
} }
} }
/**
* Get the width of the DataTables wrapper element
*
* @param {*} settings DataTables settings object
* @returns Width
*/
function _fnWrapperWidth(settings) {
return $(settings.nTableWrapper).is(':visible')
? $(settings.nTableWrapper).width()
: 0;
}
/** /**
* Get the maximum strlen for each data column * Get the maximum strlen for each data column
@@ -5855,10 +5906,14 @@
displayMaster = oSettings.aiDisplayMaster, displayMaster = oSettings.aiDisplayMaster,
aSort; aSort;
// Make sure the columns all have types defined
_fnColumnTypes(oSettings);
// Allow a specific column to be sorted, which will _not_ alter the display // Allow a specific column to be sorted, which will _not_ alter the display
// master // master
if (col !== undefined) { if (col !== undefined) {
var srcCol = oSettings.aoColumns[col]; var srcCol = oSettings.aoColumns[col];
aSort = [{ aSort = [{
src: col, src: col,
col: col, col: col,
@@ -6153,15 +6208,26 @@
return; return;
} }
// Sort state saving uses [[idx, order]] structure.
var sorting = [];
_fnSortResolve(settings, sorting, settings.aaSorting );
/* Store the interesting variables */ /* Store the interesting variables */
var columns = settings.aoColumns;
var state = { var state = {
time: +new Date(), time: +new Date(),
start: settings._iDisplayStart, start: settings._iDisplayStart,
length: settings._iDisplayLength, length: settings._iDisplayLength,
order: $.extend( true, [], settings.aaSorting ), order: sorting.map(function (sort) {
// If a column name is available, use it
return columns[sort[0]] && columns[sort[0]].sName
? [ columns[sort[0]].sName, sort[1] ]
: sort.slice();
} ),
search: $.extend({}, settings.oPreviousSearch), search: $.extend({}, settings.oPreviousSearch),
columns: settings.aoColumns.map( function ( col, i ) { columns: settings.aoColumns.map( function ( col, i ) {
return { return {
name: col.sName,
visible: col.bVisible, visible: col.bVisible,
search: $.extend({}, settings.aoPreSearchCols[i]) search: $.extend({}, settings.aoPreSearchCols[i])
}; };
@@ -6209,6 +6275,8 @@
function _fnImplementState ( settings, s, callback) { function _fnImplementState ( settings, s, callback) {
var i, ien; var i, ien;
var columns = settings.aoColumns; var columns = settings.aoColumns;
var currentNames = _pluck(settings.aoColumns, 'sName');
settings._bLoadingState = true; settings._bLoadingState = true;
// When StateRestore was introduced the state could now be implemented at any time // When StateRestore was introduced the state could now be implemented at any time
@@ -6238,13 +6306,6 @@
return; return;
} }
// Number of columns have changed - all bets are off, no restore of settings
if ( s.columns && columns.length !== s.columns.length ) {
settings._bLoadingState = false;
callback();
return;
}
// Store the saved state so it might be accessed at any time // Store the saved state so it might be accessed at any time
settings.oLoadedState = $.extend( true, {}, s ); settings.oLoadedState = $.extend( true, {}, s );
@@ -6278,10 +6339,23 @@
if ( s.order !== undefined ) { if ( s.order !== undefined ) {
settings.aaSorting = []; settings.aaSorting = [];
$.each( s.order, function ( i, col ) { $.each( s.order, function ( i, col ) {
settings.aaSorting.push( col[0] >= columns.length ? var set = [ col[0], col[1] ];
[ 0, col[1] ] :
col // A column name was stored and should be used for restore
); if (typeof col[0] === 'string') {
var idx = currentNames.indexOf(col[0]);
// Find the name from the current list of column names, or fallback to index 0
set[0] = idx >= 0
? idx
: 0;
}
else if (set[0] >= columns.length) {
// If a column name, but it is out of bounds, set to 0
set[0] = 0;
}
settings.aaSorting.push(set);
} ); } );
} }
@@ -6292,31 +6366,65 @@
// Columns // Columns
if ( s.columns ) { if ( s.columns ) {
for ( i=0, ien=s.columns.length ; i<ien ; i++ ) { var set = s.columns;
var col = s.columns[i]; var incoming = _pluck(s.columns, 'name');
// Visibility // Check if it is a 2.2 style state object with a `name` property for the columns, and if
if ( col.visible !== undefined ) { // the name was defined. If so, then create a new array that will map the state object
// If the api is defined, the table has been initialised so we need to use it rather than internal settings // given, to the current columns (don't bother if they are already matching tho).
if (api) { if (incoming.join('').length && incoming.join('') !== currentNames.join('')) {
// Don't redraw the columns on every iteration of this loop, we will do this at the end instead set = [];
api.column(i).visible(col.visible, false);
// For each column, try to find the name in the incoming array
for (i=0 ; i<currentNames.length ; i++) {
if (currentNames[i] != '') {
var idx = incoming.indexOf(currentNames[i]);
if (idx >= 0) {
set.push(s.columns[idx]);
}
else {
// No matching column name in the state's columns, so this might be a new
// column and thus can't have a state already.
set.push({});
}
} }
else { else {
columns[i].bVisible = col.visible; // If no name, but other columns did have a name, then there is no knowing
// where this one came from originally so it can't be restored.
set.push({});
}
}
}
// If the number of columns to restore is different from current, then all bets are off.
if (set.length === columns.length) {
for ( i=0, ien=set.length ; i<ien ; i++ ) {
var col = set[i];
// Visibility
if ( col.visible !== undefined ) {
// If the api is defined, the table has been initialised so we need to use it rather than internal settings
if (api) {
// Don't redraw the columns on every iteration of this loop, we will do this at the end instead
api.column(i).visible(col.visible, false);
}
else {
columns[i].bVisible = col.visible;
}
}
// Search
if ( col.search !== undefined ) {
$.extend( settings.aoPreSearchCols[i], col.search );
} }
} }
// Search // If the api is defined then we need to adjust the columns once the visibility has been changed
if ( col.search !== undefined ) { if (api) {
$.extend( settings.aoPreSearchCols[i], col.search ); api.columns.adjust();
} }
} }
// If the api is defined then we need to adjust the columns once the visibility has been changed
if (api) {
api.columns.adjust();
}
} }
settings._bLoadingState = false; settings._bLoadingState = false;
@@ -6633,6 +6741,30 @@
replace(/_ENTRIES-TOTAL_/g, settings.api.i18n('entries', '', vis) ); replace(/_ENTRIES-TOTAL_/g, settings.api.i18n('entries', '', vis) );
} }
/**
* Add elements to an array as quickly as possible, but stack stafe.
*
* @param {*} arr Array to add the data to
* @param {*} data Data array that is to be added
* @returns
*/
function _fnArrayApply(arr, data) {
if (! data) {
return;
}
// Chrome can throw a max stack error if apply is called with
// too large an array, but apply is faster.
if (data.length < 10000) {
arr.push.apply(arr, data);
}
else {
for (i=0 ; i<data.length ; i++) {
arr.push(data[i]);
}
}
}
/** /**
@@ -6825,18 +6957,7 @@
: settings; : settings;
// Initial data // Initial data
if ( data ) { _fnArrayApply(this, data);
// Chrome can throw a max stack error if apply is called with
// too large an array, but apply is faster.
if (data.length < 10000) {
this.push.apply(this, data);
}
else {
for (i=0 ; i<data.length ; i++) {
this.push(data[i]);
}
}
}
// selector // selector
this.selector = { this.selector = {
@@ -7217,7 +7338,7 @@
selector.forEach(function (sel) { selector.forEach(function (sel) {
var inner = __table_selector(sel, a); var inner = __table_selector(sel, a);
result.push.apply(result, inner); _fnArrayApply(result, inner);
}); });
return result.filter( function (item) { return result.filter( function (item) {
@@ -8071,7 +8192,7 @@
// Return an Api.rows() extended instance, so rows().nodes() etc can be used // Return an Api.rows() extended instance, so rows().nodes() etc can be used
var modRows = this.rows( -1 ); var modRows = this.rows( -1 );
modRows.pop(); modRows.pop();
modRows.push.apply(modRows, newRows); _fnArrayApply(modRows, newRows);
return modRows; return modRows;
} ); } );
@@ -8584,7 +8705,10 @@
.map( function () { .map( function () {
return _fnColumnsFromHeader( this ); // `nodes` is column index complete and in order return _fnColumnsFromHeader( this ); // `nodes` is column index complete and in order
} ) } )
.toArray(); .toArray()
.sort(function (a, b) {
return a - b;
});
if ( jqResult.length || ! s.nodeName ) { if ( jqResult.length || ! s.nodeName ) {
return jqResult; return jqResult;
@@ -8838,6 +8962,10 @@
_api_register( 'columns.adjust()', function () { _api_register( 'columns.adjust()', function () {
return this.iterator( 'table', function ( settings ) { return this.iterator( 'table', function ( settings ) {
// Force a column sizing to happen with a manual call - otherwise it can skip
// if the size hasn't changed
settings.containerWidth = -1;
_fnAdjustColumnSizing( settings ); _fnAdjustColumnSizing( settings );
}, 1 ); }, 1 );
} ); } );
@@ -9403,6 +9531,10 @@
} ); } );
} ); } );
// Can be assigned in DateTable.use() - note luxon and moment vars are in helpers.js
var __bootstrap;
var __foundation;
/** /**
* Set the libraries that DataTables uses, or the global objects. * Set the libraries that DataTables uses, or the global objects.
* Note that the arguments can be either way around (legacy support) * Note that the arguments can be either way around (legacy support)
@@ -9436,6 +9568,14 @@
case 'moment': case 'moment':
return __moment; return __moment;
case 'bootstrap':
// Use local if set, otherwise try window, which could be undefined
return __bootstrap || window.bootstrap;
case 'foundation':
// Ditto
return __foundation || window.Foundation;
default: default:
return null; return null;
} }
@@ -9445,7 +9585,7 @@
if (type === 'lib' || type === 'jq' || (module && module.fn && module.fn.jquery)) { if (type === 'lib' || type === 'jq' || (module && module.fn && module.fn.jquery)) {
$ = module; $ = module;
} }
else if (type == 'win' || (module && module.document)) { else if (type === 'win' || (module && module.document)) {
window = module; window = module;
document = module.document; document = module.document;
} }
@@ -9458,6 +9598,14 @@
else if (type === 'moment' || (module && module.isMoment)) { else if (type === 'moment' || (module && module.isMoment)) {
__moment = module; __moment = module;
} }
else if (type === 'bootstrap' || (module && module.Modal && module.Modal.NAME === 'modal'))
{
// This is currently for BS5 only. BS3/4 attach to jQuery, so no need to use `.use()`
__bootstrap = module;
}
else if (type === 'foundation' || (module && module.Reveal)) {
__foundation = module;
}
} }
/** /**
@@ -9709,12 +9857,14 @@
// Function to run either once the table becomes ready or // Function to run either once the table becomes ready or
// immediately if it is already ready. // immediately if it is already ready.
return this.tables().every(function () { return this.tables().every(function () {
var api = this;
if (this.context[0]._bInitComplete) { if (this.context[0]._bInitComplete) {
fn.call(this); fn.call(api);
} }
else { else {
this.on('init.dt.DT', function () { this.on('init.dt.DT', function () {
fn.call(this); fn.call(api);
}); });
} }
} ); } );
@@ -9748,6 +9898,11 @@
new _Api( settings ).columns().visible( true ); new _Api( settings ).columns().visible( true );
} }
// Container width change listener
if (settings.resizeObserver) {
settings.resizeObserver.disconnect();
}
// Blitz all `DT` namespaced events (these are internal events, the // Blitz all `DT` namespaced events (these are internal events, the
// lowercase, `dt` events are user subscribed and they are responsible // lowercase, `dt` events are user subscribed and they are responsible
// for removing them // for removing them
@@ -9765,20 +9920,37 @@
jqTable.append( tfoot ); jqTable.append( tfoot );
} }
// Clean up the header
$(thead).find('span.dt-column-order').remove();
$(thead).find('span.dt-column-title').each(function () {
var title = $(this).html();
$(this).parent().append(title);
$(this).remove();
});
settings.colgroup.remove(); settings.colgroup.remove();
settings.aaSorting = []; settings.aaSorting = [];
settings.aaSortingFixed = []; settings.aaSortingFixed = [];
_fnSortingClasses( settings ); _fnSortingClasses( settings );
$(jqTable).find('th, td').removeClass(
$.map(DataTable.ext.type.className, function (v) {
return v;
}).join(' ')
);
$('th, td', thead) $('th, td', thead)
.removeClass( .removeClass(
orderClasses.none + ' ' +
orderClasses.canAsc + ' ' + orderClasses.canAsc + ' ' +
orderClasses.canDesc + ' ' + orderClasses.canDesc + ' ' +
orderClasses.isAsc + ' ' + orderClasses.isAsc + ' ' +
orderClasses.isDesc orderClasses.isDesc
) )
.css('width', ''); .css('width', '')
.removeAttr('data-dt-column')
.removeAttr('aria-sort');
// Add the TR elements back into the table in their original order // Add the TR elements back into the table in their original order
jqTbody.children().detach(); jqTbody.children().detach();
@@ -9866,7 +10038,7 @@
* @type string * @type string
* @default Version number * @default Version number
*/ */
DataTable.version = "2.1.8"; DataTable.version = "2.2.2";
/** /**
* Private data store, containing all of the settings objects that are * Private data store, containing all of the settings objects that are
@@ -11969,7 +12141,13 @@
deferLoading: null, deferLoading: null,
/** Allow auto type detection */ /** Allow auto type detection */
typeDetect: true typeDetect: true,
/** ResizeObserver for the container div */
resizeObserver: null,
/** Keep a record of the last size of the container, so we can skip duplicates */
containerWidth: -1
}; };
/** /**
@@ -12111,8 +12289,8 @@
var __mlWarning = false; var __mlWarning = false;
var __luxon; // Can be assigned in DateTeble.use() var __luxon; // Can be assigned in DateTable.use()
var __moment; // Can be assigned in DateTeble.use() var __moment; // Can be assigned in DateTable.use()
/** /**
* *
@@ -12148,7 +12326,7 @@
return null; return null;
} }
dt.setLocale(locale); dt = dt.setLocale(locale);
} }
else if (! format) { else if (! format) {
// No format given, must be ISO // No format given, must be ISO
@@ -12531,6 +12709,13 @@
}); });
} }
var __diacriticHtmlSort = function (a, b) {
a = _stripHtml(a);
b = _stripHtml(b);
return __diacriticSort(a, b);
}
// //
// Built in data types // Built in data types
// //
@@ -12601,6 +12786,31 @@
}); });
DataTable.type('html-utf8', {
detect: {
allOf: function ( d ) {
return _empty( d ) || (typeof d === 'string' && d.indexOf('<') !== -1);
},
oneOf: function ( d ) {
// At least one data point must contain a `<` and a non-ASCII character
// eslint-disable-next-line compat/compat
return navigator.languages &&
! _empty( d ) &&
typeof d === 'string' &&
d.indexOf('<') !== -1 &&
typeof d === 'string' && d.match(/[^\x00-\x7F]/);
}
},
order: {
asc: __diacriticHtmlSort,
desc: function (a, b) {
return __diacriticHtmlSort(a, b) * -1;
}
},
search: _filterString(true, true)
});
DataTable.type('date', { DataTable.type('date', {
className: 'dt-type-date', className: 'dt-type-date',
detect: { detect: {
@@ -12811,6 +13021,7 @@
var indexes = columns.indexes(); var indexes = columns.indexes();
var sortDirs = columns.orderable(true).flatten(); var sortDirs = columns.orderable(true).flatten();
var orderedColumns = _pluck(sorting, 'col'); var orderedColumns = _pluck(sorting, 'col');
var tabIndex = settings.iTabIndex;
cell cell
.removeClass( .removeClass(
@@ -12868,15 +13079,20 @@
cell.removeAttr('aria-sort'); cell.removeAttr('aria-sort');
} }
cell.attr('aria-label', orderable
? col.ariaTitle + ctx.api.i18n('oAria.orderable' + ariaType)
: col.ariaTitle
);
// Make the headers tab-able for keyboard navigation // Make the headers tab-able for keyboard navigation
if (orderable) { if (orderable) {
cell.find('.dt-column-title').attr('role', 'button'); var orderSpan = cell.find('.dt-column-order');
cell.attr('tabindex', 0)
orderSpan
.attr('role', 'button')
.attr('aria-label', orderable
? col.ariaTitle + ctx.api.i18n('oAria.orderable' + ariaType)
: col.ariaTitle
);
if (tabIndex !== -1) {
orderSpan.attr('tabindex', tabIndex);
}
} }
} ); } );
} }
@@ -12890,7 +13106,7 @@
.addClass(items.className || classes.row) .addClass(items.className || classes.row)
.appendTo( container ); .appendTo( container );
$.each( items, function (key, val) { DataTable.ext.renderer.layout._forLayoutRow(items, function (key, val) {
if (key === 'id' || key === 'className') { if (key === 'id' || key === 'className') {
return; return;
} }
@@ -12921,7 +13137,31 @@
}) })
.append( val.contents ) .append( val.contents )
.appendTo( row ); .appendTo( row );
} ); });
},
// Shared for use by the styling frameworks
_forLayoutRow: function (items, fn) {
// As we are inserting dom elements, we need start / end in a
// specific order, this function is used for sorting the layout
// keys.
var layoutEnum = function (x) {
switch (x) {
case '': return 0;
case 'start': return 1;
case 'end': return 2;
default: return 3;
}
};
Object
.keys(items)
.sort(function (a, b) {
return layoutEnum(a) - layoutEnum(b);
})
.forEach(function (key) {
fn(key, items[key]);
});
} }
} }
} ); } );
@@ -13273,7 +13513,7 @@
'data-dt-idx': button, 'data-dt-idx': button,
'tabIndex': btnInfo.disabled 'tabIndex': btnInfo.disabled
? -1 ? -1
: settings.iTabIndex : settings.iTabIndex && btn.clicker[0].nodeName.toLowerCase() !== 'span'
? settings.iTabIndex ? settings.iTabIndex
: null, // `0` doesn't need a tabIndex since it is the default : null, // `0` doesn't need a tabIndex since it is the default
}); });
@@ -13307,12 +13547,16 @@
// Responsive - check if the buttons are over two lines based on the // Responsive - check if the buttons are over two lines based on the
// height of the buttons and the container. // height of the buttons and the container.
if ( if (buttonEls.length) {
buttonEls.length && // any buttons var outerHeight = $(buttonEls[0]).outerHeight();
opts.buttons > 1 && // prevent infinite
$(host).height() >= ($(buttonEls[0]).outerHeight() * 2) - 10 if (
) { opts.buttons > 1 && // prevent infinite
_pagingDraw(settings, host, $.extend({}, opts, { buttons: opts.buttons - 2 })); outerHeight > 0 && // will be 0 if hidden
$(host).height() >= (outerHeight * 2) - 10
) {
_pagingDraw(settings, host, $.extend({}, opts, { buttons: opts.buttons - 2 }));
}
} }
} }
@@ -13336,7 +13580,6 @@
switch ( button ) { switch ( button ) {
case 'ellipsis': case 'ellipsis':
o.display = '&#x2026;'; o.display = '&#x2026;';
o.disabled = true;
break; break;
case 'first': case 'first':
@@ -13523,7 +13766,7 @@
// Save text node content for macro updating // Save text node content for macro updating
var textNodes = []; var textNodes = [];
Array.from(div.find('label')[0].childNodes).forEach(function (el) { Array.prototype.slice.call(div.find('label')[0].childNodes).forEach(function (el) {
if (el.nodeType === Node.TEXT_NODE) { if (el.nodeType === Node.TEXT_NODE) {
textNodes.push({ textNodes.push({
el: el, el: el,
@@ -13541,7 +13784,6 @@
// Next, the select itself, along with the options // Next, the select itself, along with the options
var select = $('<select/>', { var select = $('<select/>', {
'name': tableId+'_length',
'aria-controls': tableId, 'aria-controls': tableId,
'class': classes.select 'class': classes.select
} ); } );
@@ -13732,41 +13974,6 @@ DataTable.ext.renderer.pagingContainer.bootstrap = function (settings, buttonEls
return $('<ul/>').addClass('pagination').append(buttonEls); return $('<ul/>').addClass('pagination').append(buttonEls);
}; };
// DataTable.ext.renderer.layout.bootstrap = function ( settings, container, items ) {
// var row = $( '<div/>', {
// "class": items.full ?
// 'row mt-2 justify-content-md-center' :
// 'row mt-2 justify-content-between'
// } )
// .appendTo( container );
// $.each( items, function (key, val) {
// var klass;
// var cellClass = '';
// // Apply start / end (left / right when ltr) margins
// if (val.table) {
// klass = 'col-12';
// }
// else if (key === 'start') {
// klass = '' + cellClass;
// }
// else if (key === 'end') {
// klass = '' + cellClass;
// }
// else {
// klass = ' ' + cellClass;
// }
// $( '<div/>', {
// id: val.id || null,
// "class": klass + ' ' + (val.className || '')
// } )
// .append( val.contents )
// .appendTo( row );
// } );
// };
return DataTable; return DataTable;
})); }));
@@ -0,0 +1,8 @@
Verify Your Email
<!---------------->
Verify this email address to finish creating your account by clicking the link below.
Verify Email Address Now: {{{url}}}
If you did not request to verify your account, you can safely ignore this email.
{{> email/email_footer_text }}
@@ -0,0 +1,24 @@
Verify Your Email
<!---------------->
{{> email/email_header }}
<table width="100%" cellpadding="0" cellspacing="0" style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
<tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
<td class="content-block" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; margin: 0; -webkit-font-smoothing: antialiased; padding: 0 0 10px; -webkit-text-size-adjust: none; text-align: center;" valign="top" align="center">
Verify this email address to finish creating your account by clicking the link below.
</td>
</tr>
<tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
<td class="content-block" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; margin: 0; -webkit-font-smoothing: antialiased; padding: 0 0 10px; -webkit-text-size-adjust: none; text-align: center;" valign="top" align="center">
<a href="{{{url}}}"
clicktracking=off target="_blank" style="color: #ffffff; text-decoration: none; text-align: center; cursor: pointer; display: inline-block; border-radius: 5px; background-color: #3c8dbc; border-color: #3c8dbc; border-style: solid; border-width: 10px 20px; margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
Verify Email Address Now
</a>
</td>
</tr>
<tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
<td class="content-block last" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; margin: 0; -webkit-font-smoothing: antialiased; padding: 0; -webkit-text-size-adjust: none; text-align: center;" valign="top" align="center">
If you did not request to verify your account, you can safely ignore this email.
</td>
</tr>
</table>
{{> email/email_footer }}
+16 -6
View File
@@ -21,7 +21,15 @@ a[href$="/settings/sponsored-families"] {
} }
/* Hide the `Enterprise Single Sign-On` button on the login page */ /* Hide the `Enterprise Single Sign-On` button on the login page */
a[routerlink="/sso"] { app-root form.ng-untouched button.\!tw-text-primary-600:nth-child(4) {
@extend %vw-hide;
}
/* Hide Log in with passkey on the login page */
app-root form.ng-untouched a[routerlink="/login-with-passkey"] {
@extend %vw-hide;
}
/* Hide the or text followed by the two buttons hidden above */
app-root form.ng-untouched > div:nth-child(1) > div:nth-child(3) > div:nth-child(2) {
@extend %vw-hide; @extend %vw-hide;
} }
@@ -73,11 +81,6 @@ bit-dialog div.tw-col-span-4:has(input[formcontrolname*="access"], input[formcon
@extend %vw-hide; @extend %vw-hide;
} }
/* Hide Log in with passkey */
app-login div.tw-flex:nth-child(4) {
@extend %vw-hide;
}
/* Change collapsed menu icon to Vaultwarden */ /* Change collapsed menu icon to Vaultwarden */
bit-nav-logo bit-nav-item a:before { bit-nav-logo bit-nav-item a:before {
content: ""; content: "";
@@ -93,12 +96,19 @@ bit-nav-logo bit-nav-item .bwi-shield {
/**** END Static Vaultwarden Changes ****/ /**** END Static Vaultwarden Changes ****/
/**** START Dynamic Vaultwarden Changes ****/ /**** START Dynamic Vaultwarden Changes ****/
{{#if signup_disabled}} {{#if signup_disabled}}
/* From web vault 2025.1.2 and onwards, the signup button is hidden
when signups are disabled as the web vault checks the /api/config endpoint.
Note that the clients tend to aggressively cache this endpoint, so it might
take a while for the change to take effect. To avoid the button appearing
when it shouldn't, we'll keep this style in place for a couple of versions */
{{#if webver "<2025.3.0"}}
/* Hide the register link on the login screen */ /* Hide the register link on the login screen */
app-login form div + div + div + div + hr, app-login form div + div + div + div + hr,
app-login form div + div + div + div + hr + p { app-login form div + div + div + div + hr + p {
@extend %vw-hide; @extend %vw-hide;
} }
{{/if}} {{/if}}
{{/if}}
{{#unless mail_enabled}} {{#unless mail_enabled}}
/* Hide `Email` 2FA if mail is not enabled */ /* Hide `Email` 2FA if mail is not enabled */