mirror of
https://github.com/dani-garcia/vaultwarden.git
synced 2026-06-01 16:20:16 +03:00
Compare commits
26 Commits
1.33.2
...
ad8484a2d5
| Author | SHA1 | Date | |
|---|---|---|---|
| ad8484a2d5 | |||
| 9813e480c0 | |||
| bfe172702a | |||
| df42b6d6b0 | |||
| 2697fe8aba | |||
| 674e444d67 | |||
| 0d16da440d | |||
| 66cf179bca | |||
| 025bb90f8f | |||
| d5039d9c17 | |||
| e7c796a660 | |||
| bbbd2f6d15 | |||
| a2d7895586 | |||
| 8a0cb1137e | |||
| f960bf59bb | |||
| 3a1f1bae00 | |||
| 8dfe805954 | |||
| 07b869b3ef | |||
| 2a18665288 | |||
| 71952a4ab5 | |||
| 994d157064 | |||
| 1dae6093c9 | |||
| 6edceb5f7a | |||
| 359a4a088a | |||
| 3baffeee9a | |||
| d5c353427d |
+7
-2
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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:
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
+41
-32
@@ -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
|
||||||
|
|||||||
@@ -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())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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"),
|
||||||
|
|||||||
@@ -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!(
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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>]::*;
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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
@@ -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() {
|
||||||
|
|||||||
Vendored
+116
-111
@@ -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 {
|
||||||
|
|||||||
Vendored
+14
-12
@@ -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,
|
||||||
|
|||||||
Vendored
+355
-148
@@ -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 = '…';
|
o.display = '…';
|
||||||
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 }}
|
||||||
@@ -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 */
|
||||||
|
|||||||
Reference in New Issue
Block a user