mirror of
https://github.com/dani-garcia/vaultwarden.git
synced 2025-11-30 16:42:32 +02:00
Compare commits
13 Commits
f9751a0a1d
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
07569a06da | ||
|
|
cb2f5741ac | ||
|
|
c9d527d84f | ||
|
|
7c7f4f5d4f | ||
|
|
aad1f19b45 | ||
|
|
35e1a306f3 | ||
|
|
7f7b412220 | ||
|
|
bb41f64c0a | ||
|
|
319d982113 | ||
|
|
95a0c667e4 | ||
|
|
b519832086 | ||
|
|
2ee40d6105 | ||
|
|
0182567a62 |
@@ -376,6 +376,7 @@
|
|||||||
## - "inline-menu-totp": Enable the use of inline menu TOTP codes in the browser extension.
|
## - "inline-menu-totp": Enable the use of inline menu TOTP codes in the browser extension.
|
||||||
## - "ssh-agent": Enable SSH agent support on Desktop. (Needs desktop >=2024.12.0)
|
## - "ssh-agent": Enable SSH agent support on Desktop. (Needs desktop >=2024.12.0)
|
||||||
## - "ssh-key-vault-item": Enable the creation and use of SSH key vault items. (Needs clients >=2024.12.0)
|
## - "ssh-key-vault-item": Enable the creation and use of SSH key vault items. (Needs clients >=2024.12.0)
|
||||||
|
## - "pm-25373-windows-biometrics-v2": Enable the new implementation of biometrics on Windows. (Needs desktop >= 2025.11.0)
|
||||||
## - "export-attachments": Enable support for exporting attachments (Clients >=2025.4.0)
|
## - "export-attachments": Enable support for exporting attachments (Clients >=2025.4.0)
|
||||||
## - "anon-addy-self-host-alias": Enable configuring self-hosted Anon Addy alias generator. (Needs Android >=2025.3.0, iOS >=2025.4.0)
|
## - "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)
|
## - "simple-login-self-host-alias": Enable configuring self-hosted Simple Login alias generator. (Needs Android >=2025.3.0, iOS >=2025.4.0)
|
||||||
@@ -471,6 +472,11 @@
|
|||||||
## Setting this to true will enforce the Single Org Policy to be enabled before you can enable the Reset Password policy.
|
## Setting this to true will enforce the Single Org Policy to be enabled before you can enable the Reset Password policy.
|
||||||
# ENFORCE_SINGLE_ORG_WITH_RESET_PW_POLICY=false
|
# ENFORCE_SINGLE_ORG_WITH_RESET_PW_POLICY=false
|
||||||
|
|
||||||
|
## Prefer IPv6 (AAAA) resolving
|
||||||
|
## This settings configures the DNS resolver to resolve IPv6 first, and if not available try IPv4
|
||||||
|
## This could be useful in IPv6 only environments.
|
||||||
|
# DNS_PREFER_IPV6=false
|
||||||
|
|
||||||
#####################################
|
#####################################
|
||||||
### SSO settings (OpenID Connect) ###
|
### SSO settings (OpenID Connect) ###
|
||||||
#####################################
|
#####################################
|
||||||
|
|||||||
8
.github/workflows/build.yml
vendored
8
.github/workflows/build.yml
vendored
@@ -54,7 +54,7 @@ jobs:
|
|||||||
|
|
||||||
# Checkout the repo
|
# Checkout the repo
|
||||||
- name: "Checkout"
|
- name: "Checkout"
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 #v5.0.0
|
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 #v6.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
@@ -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@6d653acede28d24f02e3cd41383119e8b1b35921 # master @ Sep 16, 2025, 8:37 PM GMT+2
|
uses: dtolnay/rust-toolchain@0b1efabc08b657293548b77fb76cc02d26091c7e # master @ Nov 20, 2025, 7:02 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@6d653acede28d24f02e3cd41383119e8b1b35921 # master @ Sep 16, 2025, 8:37 PM GMT+2
|
uses: dtolnay/rust-toolchain@0b1efabc08b657293548b77fb76cc02d26091c7e # master @ Nov 20, 2025, 7:02 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@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
|
uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
|
||||||
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.
|
||||||
|
|||||||
2
.github/workflows/check-templates.yml
vendored
2
.github/workflows/check-templates.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
# Checkout the repo
|
# Checkout the repo
|
||||||
- name: "Checkout"
|
- name: "Checkout"
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 #v5.0.0
|
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 #v6.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
# End Checkout the repo
|
# End Checkout the repo
|
||||||
|
|||||||
2
.github/workflows/hadolint.yml
vendored
2
.github/workflows/hadolint.yml
vendored
@@ -34,7 +34,7 @@ jobs:
|
|||||||
# End Download hadolint
|
# End Download hadolint
|
||||||
# Checkout the repo
|
# Checkout the repo
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 #v5.0.0
|
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 #v6.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
# End Checkout the repo
|
# End Checkout the repo
|
||||||
|
|||||||
332
.github/workflows/release.yml
vendored
332
.github/workflows/release.yml
vendored
@@ -16,26 +16,11 @@ concurrency:
|
|||||||
# Don't cancel other runs when creating a tag
|
# Don't cancel other runs when creating a tag
|
||||||
cancel-in-progress: ${{ github.ref_type == 'branch' }}
|
cancel-in-progress: ${{ github.ref_type == 'branch' }}
|
||||||
|
|
||||||
jobs:
|
defaults:
|
||||||
docker-build:
|
run:
|
||||||
name: Build Vaultwarden containers
|
shell: bash
|
||||||
if: ${{ github.repository == 'dani-garcia/vaultwarden' }}
|
|
||||||
permissions:
|
|
||||||
packages: write # Needed to upload packages and artifacts
|
|
||||||
contents: read
|
|
||||||
attestations: write # Needed to generate an artifact attestation for a build
|
|
||||||
id-token: write # Needed to mint the OIDC token necessary to request a Sigstore signing certificate
|
|
||||||
runs-on: ubuntu-24.04
|
|
||||||
timeout-minutes: 120
|
|
||||||
# Start a local docker registry to extract the compiled binaries to upload as artifacts and attest them
|
|
||||||
services:
|
|
||||||
registry:
|
|
||||||
image: registry@sha256:1fc7de654f2ac1247f0b67e8a459e273b0993be7d2beda1f3f56fbf1001ed3e7 # v3.0.0
|
|
||||||
ports:
|
|
||||||
- 5000:5000
|
|
||||||
env:
|
env:
|
||||||
SOURCE_COMMIT: ${{ github.sha }}
|
|
||||||
SOURCE_REPOSITORY_URL: "https://github.com/${{ github.repository }}"
|
|
||||||
# The *_REPO variables need to be configured as repository variables
|
# The *_REPO variables need to be configured as repository variables
|
||||||
# Append `/settings/variables/actions` to your repo url
|
# Append `/settings/variables/actions` to your repo url
|
||||||
# DOCKERHUB_REPO needs to be 'index.docker.io/<user>/<repo>'
|
# DOCKERHUB_REPO needs to be 'index.docker.io/<user>/<repo>'
|
||||||
@@ -47,9 +32,33 @@ jobs:
|
|||||||
# QUAY_REPO needs to be 'quay.io/<user>/<repo>'
|
# QUAY_REPO needs to be 'quay.io/<user>/<repo>'
|
||||||
# Check for Quay.io credentials in secrets
|
# Check for Quay.io credentials in secrets
|
||||||
HAVE_QUAY_LOGIN: ${{ vars.QUAY_REPO != '' && secrets.QUAY_USERNAME != '' && secrets.QUAY_TOKEN != '' }}
|
HAVE_QUAY_LOGIN: ${{ vars.QUAY_REPO != '' && secrets.QUAY_USERNAME != '' && secrets.QUAY_TOKEN != '' }}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
docker-build:
|
||||||
|
name: Build Vaultwarden containers
|
||||||
|
if: ${{ github.repository == 'dani-garcia/vaultwarden' }}
|
||||||
|
permissions:
|
||||||
|
packages: write # Needed to upload packages and artifacts
|
||||||
|
contents: read
|
||||||
|
attestations: write # Needed to generate an artifact attestation for a build
|
||||||
|
id-token: write # Needed to mint the OIDC token necessary to request a Sigstore signing certificate
|
||||||
|
runs-on: ${{ contains(matrix.arch, 'arm') && 'ubuntu-24.04-arm' || 'ubuntu-24.04' }}
|
||||||
|
timeout-minutes: 120
|
||||||
|
# Start a local docker registry to extract the compiled binaries to upload as artifacts and attest them
|
||||||
|
services:
|
||||||
|
registry:
|
||||||
|
image: registry@sha256:1fc7de654f2ac1247f0b67e8a459e273b0993be7d2beda1f3f56fbf1001ed3e7 # v3.0.0
|
||||||
|
ports:
|
||||||
|
- 5000:5000
|
||||||
|
env:
|
||||||
|
SOURCE_COMMIT: ${{ github.sha }}
|
||||||
|
SOURCE_REPOSITORY_URL: "https://github.com/${{ github.repository }}"
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
|
arch: ["amd64", "arm64", "arm/v7", "arm/v6"]
|
||||||
base_image: ["debian","alpine"]
|
base_image: ["debian","alpine"]
|
||||||
|
outputs:
|
||||||
|
base-tags: ${{ steps.determine-version.outputs.BASE_TAGS }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Initialize QEMU binfmt support
|
- name: Initialize QEMU binfmt support
|
||||||
@@ -72,23 +81,32 @@ jobs:
|
|||||||
|
|
||||||
# Checkout the repo
|
# Checkout the repo
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 #v5.0.0
|
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 #v6.0.0
|
||||||
# We need fetch-depth of 0 so we also get all the tag metadata
|
# We need fetch-depth of 0 so we also get all the tag metadata
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
|
# Normalize the architecture string for use in paths and cache keys
|
||||||
|
- name: Normalize architecture string
|
||||||
|
env:
|
||||||
|
MATRIX_ARCH: ${{ matrix.arch }}
|
||||||
|
run: |
|
||||||
|
# Replace slashes with nothing to create a safe string for paths/cache keys
|
||||||
|
NORMALIZED_ARCH="${MATRIX_ARCH//\/}"
|
||||||
|
echo "NORMALIZED_ARCH=${NORMALIZED_ARCH}" | tee -a "${GITHUB_ENV}"
|
||||||
|
|
||||||
# Determine Base Tags and Source Version
|
# Determine Base Tags and Source Version
|
||||||
- name: Determine Base Tags and Source Version
|
- name: Determine Base Tags and Source Version
|
||||||
shell: bash
|
id: determine-version
|
||||||
env:
|
env:
|
||||||
REF_TYPE: ${{ github.ref_type }}
|
REF_TYPE: ${{ github.ref_type }}
|
||||||
run: |
|
run: |
|
||||||
# Check which main tag we are going to build determined by ref_type
|
# Check which main tag we are going to build determined by ref_type
|
||||||
if [[ "${REF_TYPE}" == "tag" ]]; then
|
if [[ "${REF_TYPE}" == "tag" ]]; then
|
||||||
echo "BASE_TAGS=latest,${GITHUB_REF#refs/*/}" | tee -a "${GITHUB_ENV}"
|
echo "BASE_TAGS=latest,${GITHUB_REF#refs/*/}" | tee -a "${GITHUB_OUTPUT}"
|
||||||
elif [[ "${REF_TYPE}" == "branch" ]]; then
|
elif [[ "${REF_TYPE}" == "branch" ]]; then
|
||||||
echo "BASE_TAGS=testing" | tee -a "${GITHUB_ENV}"
|
echo "BASE_TAGS=testing" | tee -a "${GITHUB_OUTPUT}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Get the Source Version for this release
|
# Get the Source Version for this release
|
||||||
@@ -111,7 +129,6 @@ jobs:
|
|||||||
|
|
||||||
- name: Add registry for DockerHub
|
- name: Add registry for DockerHub
|
||||||
if: ${{ env.HAVE_DOCKERHUB_LOGIN == 'true' }}
|
if: ${{ env.HAVE_DOCKERHUB_LOGIN == 'true' }}
|
||||||
shell: bash
|
|
||||||
env:
|
env:
|
||||||
DOCKERHUB_REPO: ${{ vars.DOCKERHUB_REPO }}
|
DOCKERHUB_REPO: ${{ vars.DOCKERHUB_REPO }}
|
||||||
run: |
|
run: |
|
||||||
@@ -128,7 +145,6 @@ jobs:
|
|||||||
|
|
||||||
- name: Add registry for ghcr.io
|
- name: Add registry for ghcr.io
|
||||||
if: ${{ env.HAVE_GHCR_LOGIN == 'true' }}
|
if: ${{ env.HAVE_GHCR_LOGIN == 'true' }}
|
||||||
shell: bash
|
|
||||||
env:
|
env:
|
||||||
GHCR_REPO: ${{ vars.GHCR_REPO }}
|
GHCR_REPO: ${{ vars.GHCR_REPO }}
|
||||||
run: |
|
run: |
|
||||||
@@ -145,23 +161,22 @@ jobs:
|
|||||||
|
|
||||||
- name: Add registry for Quay.io
|
- name: Add registry for Quay.io
|
||||||
if: ${{ env.HAVE_QUAY_LOGIN == 'true' }}
|
if: ${{ env.HAVE_QUAY_LOGIN == 'true' }}
|
||||||
shell: bash
|
|
||||||
env:
|
env:
|
||||||
QUAY_REPO: ${{ vars.QUAY_REPO }}
|
QUAY_REPO: ${{ vars.QUAY_REPO }}
|
||||||
run: |
|
run: |
|
||||||
echo "CONTAINER_REGISTRIES=${CONTAINER_REGISTRIES:+${CONTAINER_REGISTRIES},}${QUAY_REPO}" | tee -a "${GITHUB_ENV}"
|
echo "CONTAINER_REGISTRIES=${CONTAINER_REGISTRIES:+${CONTAINER_REGISTRIES},}${QUAY_REPO}" | tee -a "${GITHUB_ENV}"
|
||||||
|
|
||||||
- name: Configure build cache from/to
|
- name: Configure build cache from/to
|
||||||
shell: bash
|
|
||||||
env:
|
env:
|
||||||
GHCR_REPO: ${{ vars.GHCR_REPO }}
|
GHCR_REPO: ${{ vars.GHCR_REPO }}
|
||||||
BASE_IMAGE: ${{ matrix.base_image }}
|
BASE_IMAGE: ${{ matrix.base_image }}
|
||||||
|
NORMALIZED_ARCH: ${{ env.NORMALIZED_ARCH }}
|
||||||
run: |
|
run: |
|
||||||
#
|
#
|
||||||
# Check if there is a GitHub Container Registry Login and use it for caching
|
# Check if there is a GitHub Container Registry Login and use it for caching
|
||||||
if [[ -n "${HAVE_GHCR_LOGIN}" ]]; then
|
if [[ -n "${HAVE_GHCR_LOGIN}" ]]; then
|
||||||
echo "BAKE_CACHE_FROM=type=registry,ref=${GHCR_REPO}-buildcache:${BASE_IMAGE}" | tee -a "${GITHUB_ENV}"
|
echo "BAKE_CACHE_FROM=type=registry,ref=${GHCR_REPO}-buildcache:${BASE_IMAGE}-${NORMALIZED_ARCH}" | tee -a "${GITHUB_ENV}"
|
||||||
echo "BAKE_CACHE_TO=type=registry,ref=${GHCR_REPO}-buildcache:${BASE_IMAGE},compression=zstd,mode=max" | tee -a "${GITHUB_ENV}"
|
echo "BAKE_CACHE_TO=type=registry,ref=${GHCR_REPO}-buildcache:${BASE_IMAGE}-${NORMALIZED_ARCH},compression=zstd,mode=max" | tee -a "${GITHUB_ENV}"
|
||||||
else
|
else
|
||||||
echo "BAKE_CACHE_FROM="
|
echo "BAKE_CACHE_FROM="
|
||||||
echo "BAKE_CACHE_TO="
|
echo "BAKE_CACHE_TO="
|
||||||
@@ -169,31 +184,45 @@ jobs:
|
|||||||
#
|
#
|
||||||
|
|
||||||
- name: Add localhost registry
|
- name: Add localhost registry
|
||||||
shell: bash
|
|
||||||
run: |
|
run: |
|
||||||
echo "CONTAINER_REGISTRIES=${CONTAINER_REGISTRIES:+${CONTAINER_REGISTRIES},}localhost:5000/vaultwarden/server" | tee -a "${GITHUB_ENV}"
|
echo "CONTAINER_REGISTRIES=${CONTAINER_REGISTRIES:+${CONTAINER_REGISTRIES},}localhost:5000/vaultwarden/server" | tee -a "${GITHUB_ENV}"
|
||||||
|
|
||||||
|
- name: Generate tags
|
||||||
|
id: tags
|
||||||
|
env:
|
||||||
|
CONTAINER_REGISTRIES: "${{ env.CONTAINER_REGISTRIES }}"
|
||||||
|
run: |
|
||||||
|
# Convert comma-separated list to newline-separated set commands
|
||||||
|
TAGS=$(echo "${CONTAINER_REGISTRIES}" | tr ',' '\n' | sed "s|.*|*.tags=&|")
|
||||||
|
|
||||||
|
# Output for use in next step
|
||||||
|
{
|
||||||
|
echo "TAGS<<EOF"
|
||||||
|
echo "$TAGS"
|
||||||
|
echo "EOF"
|
||||||
|
} >> "$GITHUB_ENV"
|
||||||
|
|
||||||
- name: Bake ${{ matrix.base_image }} containers
|
- name: Bake ${{ matrix.base_image }} containers
|
||||||
id: bake_vw
|
id: bake_vw
|
||||||
uses: docker/bake-action@3acf805d94d93a86cce4ca44798a76464a75b88c # v6.9.0
|
uses: docker/bake-action@5be5f02ff8819ecd3092ea6b2e6261c31774f2b4 # v6.10.0
|
||||||
env:
|
env:
|
||||||
BASE_TAGS: "${{ env.BASE_TAGS }}"
|
BASE_TAGS: "${{ steps.determine-version.outputs.BASE_TAGS }}"
|
||||||
SOURCE_COMMIT: "${{ env.SOURCE_COMMIT }}"
|
SOURCE_COMMIT: "${{ env.SOURCE_COMMIT }}"
|
||||||
SOURCE_VERSION: "${{ env.SOURCE_VERSION }}"
|
SOURCE_VERSION: "${{ env.SOURCE_VERSION }}"
|
||||||
SOURCE_REPOSITORY_URL: "${{ env.SOURCE_REPOSITORY_URL }}"
|
SOURCE_REPOSITORY_URL: "${{ env.SOURCE_REPOSITORY_URL }}"
|
||||||
CONTAINER_REGISTRIES: "${{ env.CONTAINER_REGISTRIES }}"
|
|
||||||
with:
|
with:
|
||||||
pull: true
|
pull: true
|
||||||
push: true
|
|
||||||
source: .
|
source: .
|
||||||
files: docker/docker-bake.hcl
|
files: docker/docker-bake.hcl
|
||||||
targets: "${{ matrix.base_image }}-multi"
|
targets: "${{ matrix.base_image }}-multi"
|
||||||
set: |
|
set: |
|
||||||
*.cache-from=${{ env.BAKE_CACHE_FROM }}
|
*.cache-from=${{ env.BAKE_CACHE_FROM }}
|
||||||
*.cache-to=${{ env.BAKE_CACHE_TO }}
|
*.cache-to=${{ env.BAKE_CACHE_TO }}
|
||||||
|
*.platform=linux/${{ matrix.arch }}
|
||||||
|
${{ env.TAGS }}
|
||||||
|
*.output=type=image,push-by-digest=true,name-canonical=true,push=true,compression=zstd
|
||||||
|
|
||||||
- name: Extract digest SHA
|
- name: Extract digest SHA
|
||||||
shell: bash
|
|
||||||
env:
|
env:
|
||||||
BAKE_METADATA: ${{ steps.bake_vw.outputs.metadata }}
|
BAKE_METADATA: ${{ steps.bake_vw.outputs.metadata }}
|
||||||
BASE_IMAGE: ${{ matrix.base_image }}
|
BASE_IMAGE: ${{ matrix.base_image }}
|
||||||
@@ -201,38 +230,30 @@ jobs:
|
|||||||
GET_DIGEST_SHA="$(jq -r --arg base "$BASE_IMAGE" '.[$base + "-multi"]."containerimage.digest"' <<< "${BAKE_METADATA}")"
|
GET_DIGEST_SHA="$(jq -r --arg base "$BASE_IMAGE" '.[$base + "-multi"]."containerimage.digest"' <<< "${BAKE_METADATA}")"
|
||||||
echo "DIGEST_SHA=${GET_DIGEST_SHA}" | tee -a "${GITHUB_ENV}"
|
echo "DIGEST_SHA=${GET_DIGEST_SHA}" | tee -a "${GITHUB_ENV}"
|
||||||
|
|
||||||
# Attest container images
|
- name: Export digest
|
||||||
- name: Attest - docker.io - ${{ matrix.base_image }}
|
env:
|
||||||
if: ${{ env.HAVE_DOCKERHUB_LOGIN == 'true' && steps.bake_vw.outputs.metadata != ''}}
|
DIGEST_SHA: ${{ env.DIGEST_SHA }}
|
||||||
uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a # v3.0.0
|
RUNNER_TEMP: ${{ runner.temp }}
|
||||||
with:
|
run: |
|
||||||
subject-name: ${{ vars.DOCKERHUB_REPO }}
|
mkdir -p "${RUNNER_TEMP}"/digests
|
||||||
subject-digest: ${{ env.DIGEST_SHA }}
|
digest="${DIGEST_SHA}"
|
||||||
push-to-registry: true
|
touch "${RUNNER_TEMP}/digests/${digest#sha256:}"
|
||||||
|
|
||||||
- name: Attest - ghcr.io - ${{ matrix.base_image }}
|
- name: Upload digest
|
||||||
if: ${{ env.HAVE_GHCR_LOGIN == 'true' && steps.bake_vw.outputs.metadata != ''}}
|
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||||
uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a # v3.0.0
|
|
||||||
with:
|
with:
|
||||||
subject-name: ${{ vars.GHCR_REPO }}
|
name: digests-${{ env.NORMALIZED_ARCH }}-${{ matrix.base_image }}
|
||||||
subject-digest: ${{ env.DIGEST_SHA }}
|
path: ${{ runner.temp }}/digests/*
|
||||||
push-to-registry: true
|
if-no-files-found: error
|
||||||
|
retention-days: 1
|
||||||
- name: Attest - quay.io - ${{ matrix.base_image }}
|
|
||||||
if: ${{ env.HAVE_QUAY_LOGIN == 'true' && steps.bake_vw.outputs.metadata != ''}}
|
|
||||||
uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a # v3.0.0
|
|
||||||
with:
|
|
||||||
subject-name: ${{ vars.QUAY_REPO }}
|
|
||||||
subject-digest: ${{ env.DIGEST_SHA }}
|
|
||||||
push-to-registry: true
|
|
||||||
|
|
||||||
|
|
||||||
# Extract the Alpine binaries from the containers
|
# Extract the Alpine binaries from the containers
|
||||||
- name: Extract binaries
|
- name: Extract binaries
|
||||||
shell: bash
|
|
||||||
env:
|
env:
|
||||||
REF_TYPE: ${{ github.ref_type }}
|
REF_TYPE: ${{ github.ref_type }}
|
||||||
BASE_IMAGE: ${{ matrix.base_image }}
|
BASE_IMAGE: ${{ matrix.base_image }}
|
||||||
|
DIGEST_SHA: ${{ env.DIGEST_SHA }}
|
||||||
|
NORMALIZED_ARCH: ${{ env.NORMALIZED_ARCH }}
|
||||||
run: |
|
run: |
|
||||||
# Check which main tag we are going to build determined by ref_type
|
# Check which main tag we are going to build determined by ref_type
|
||||||
if [[ "${REF_TYPE}" == "tag" ]]; then
|
if [[ "${REF_TYPE}" == "tag" ]]; then
|
||||||
@@ -246,60 +267,151 @@ jobs:
|
|||||||
EXTRACT_TAG="${EXTRACT_TAG}-alpine"
|
EXTRACT_TAG="${EXTRACT_TAG}-alpine"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# After each extraction the image is removed.
|
CONTAINER_ID="$(docker create "localhost:5000/vaultwarden/server:${EXTRACT_TAG}@${DIGEST_SHA}")"
|
||||||
# This is needed because using different platforms doesn't trigger a new pull/download
|
|
||||||
|
|
||||||
# Extract amd64 binary
|
# Copy the binary
|
||||||
docker create --name amd64 --platform=linux/amd64 "localhost:5000/vaultwarden/server:${EXTRACT_TAG}"
|
docker cp "$CONTAINER_ID":/vaultwarden vaultwarden-"${NORMALIZED_ARCH}"
|
||||||
docker cp amd64:/vaultwarden vaultwarden-amd64-${BASE_IMAGE}
|
|
||||||
docker rm --force amd64
|
|
||||||
docker rmi --force "localhost:5000/vaultwarden/server:${EXTRACT_TAG}"
|
|
||||||
|
|
||||||
# Extract arm64 binary
|
# Clean up
|
||||||
docker create --name arm64 --platform=linux/arm64 "localhost:5000/vaultwarden/server:${EXTRACT_TAG}"
|
docker rm "$CONTAINER_ID"
|
||||||
docker cp arm64:/vaultwarden vaultwarden-arm64-${BASE_IMAGE}
|
|
||||||
docker rm --force arm64
|
|
||||||
docker rmi --force "localhost:5000/vaultwarden/server:${EXTRACT_TAG}"
|
|
||||||
|
|
||||||
# Extract armv7 binary
|
|
||||||
docker create --name armv7 --platform=linux/arm/v7 "localhost:5000/vaultwarden/server:${EXTRACT_TAG}"
|
|
||||||
docker cp armv7:/vaultwarden vaultwarden-armv7-${BASE_IMAGE}
|
|
||||||
docker rm --force armv7
|
|
||||||
docker rmi --force "localhost:5000/vaultwarden/server:${EXTRACT_TAG}"
|
|
||||||
|
|
||||||
# Extract armv6 binary
|
|
||||||
docker create --name armv6 --platform=linux/arm/v6 "localhost:5000/vaultwarden/server:${EXTRACT_TAG}"
|
|
||||||
docker cp armv6:/vaultwarden vaultwarden-armv6-${BASE_IMAGE}
|
|
||||||
docker rm --force armv6
|
|
||||||
docker rmi --force "localhost:5000/vaultwarden/server:${EXTRACT_TAG}"
|
|
||||||
|
|
||||||
# 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: Attest binaries
|
||||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
|
||||||
with:
|
|
||||||
name: vaultwarden-${{ env.SOURCE_VERSION }}-linux-amd64-${{ matrix.base_image }}
|
|
||||||
path: vaultwarden-amd64-${{ matrix.base_image }}
|
|
||||||
|
|
||||||
- name: "Upload arm64 artifact ${{ matrix.base_image }}"
|
|
||||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
|
||||||
with:
|
|
||||||
name: vaultwarden-${{ env.SOURCE_VERSION }}-linux-arm64-${{ matrix.base_image }}
|
|
||||||
path: vaultwarden-arm64-${{ matrix.base_image }}
|
|
||||||
|
|
||||||
- name: "Upload armv7 artifact ${{ matrix.base_image }}"
|
|
||||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
|
||||||
with:
|
|
||||||
name: vaultwarden-${{ env.SOURCE_VERSION }}-linux-armv7-${{ matrix.base_image }}
|
|
||||||
path: vaultwarden-armv7-${{ matrix.base_image }}
|
|
||||||
|
|
||||||
- name: "Upload armv6 artifact ${{ matrix.base_image }}"
|
|
||||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
|
||||||
with:
|
|
||||||
name: vaultwarden-${{ env.SOURCE_VERSION }}-linux-armv6-${{ matrix.base_image }}
|
|
||||||
path: vaultwarden-armv6-${{ matrix.base_image }}
|
|
||||||
|
|
||||||
- name: "Attest artifacts ${{ matrix.base_image }}"
|
|
||||||
uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a # v3.0.0
|
uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a # v3.0.0
|
||||||
with:
|
with:
|
||||||
subject-path: vaultwarden-*
|
subject-path: vaultwarden-${{ env.NORMALIZED_ARCH }}
|
||||||
# End Upload artifacts to Github Actions
|
|
||||||
|
- name: Upload binaries as artifacts
|
||||||
|
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||||
|
with:
|
||||||
|
name: vaultwarden-${{ env.SOURCE_VERSION }}-linux-${{ env.NORMALIZED_ARCH }}-${{ matrix.base_image }}
|
||||||
|
path: vaultwarden-${{ env.NORMALIZED_ARCH }}
|
||||||
|
|
||||||
|
merge-manifests:
|
||||||
|
name: Merge manifests
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: docker-build
|
||||||
|
|
||||||
|
env:
|
||||||
|
BASE_TAGS: ${{ needs.docker-build.outputs.base-tags }}
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
packages: write # Needed to upload packages and artifacts
|
||||||
|
attestations: write # Needed to generate an artifact attestation for a build
|
||||||
|
id-token: write # Needed to mint the OIDC token necessary to request a Sigstore signing certificate
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
base_image: ["debian","alpine"]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Download digests
|
||||||
|
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
|
||||||
|
with:
|
||||||
|
path: ${{ runner.temp }}/digests
|
||||||
|
pattern: digests-*-${{ matrix.base_image }}
|
||||||
|
merge-multiple: true
|
||||||
|
|
||||||
|
# Login to Docker Hub
|
||||||
|
- name: Login to Docker Hub
|
||||||
|
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
if: ${{ env.HAVE_DOCKERHUB_LOGIN == 'true' }}
|
||||||
|
|
||||||
|
- name: Add registry for DockerHub
|
||||||
|
if: ${{ env.HAVE_DOCKERHUB_LOGIN == 'true' }}
|
||||||
|
env:
|
||||||
|
DOCKERHUB_REPO: ${{ vars.DOCKERHUB_REPO }}
|
||||||
|
run: |
|
||||||
|
echo "CONTAINER_REGISTRIES=${DOCKERHUB_REPO}" | tee -a "${GITHUB_ENV}"
|
||||||
|
|
||||||
|
# Login to GitHub Container Registry
|
||||||
|
- name: Login to GitHub Container Registry
|
||||||
|
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.repository_owner }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
if: ${{ env.HAVE_GHCR_LOGIN == 'true' }}
|
||||||
|
|
||||||
|
- name: Add registry for ghcr.io
|
||||||
|
if: ${{ env.HAVE_GHCR_LOGIN == 'true' }}
|
||||||
|
env:
|
||||||
|
GHCR_REPO: ${{ vars.GHCR_REPO }}
|
||||||
|
run: |
|
||||||
|
echo "CONTAINER_REGISTRIES=${CONTAINER_REGISTRIES:+${CONTAINER_REGISTRIES},}${GHCR_REPO}" | tee -a "${GITHUB_ENV}"
|
||||||
|
|
||||||
|
# Login to Quay.io
|
||||||
|
- name: Login to Quay.io
|
||||||
|
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
||||||
|
with:
|
||||||
|
registry: quay.io
|
||||||
|
username: ${{ secrets.QUAY_USERNAME }}
|
||||||
|
password: ${{ secrets.QUAY_TOKEN }}
|
||||||
|
if: ${{ env.HAVE_QUAY_LOGIN == 'true' }}
|
||||||
|
|
||||||
|
- name: Add registry for Quay.io
|
||||||
|
if: ${{ env.HAVE_QUAY_LOGIN == 'true' }}
|
||||||
|
env:
|
||||||
|
QUAY_REPO: ${{ vars.QUAY_REPO }}
|
||||||
|
run: |
|
||||||
|
echo "CONTAINER_REGISTRIES=${CONTAINER_REGISTRIES:+${CONTAINER_REGISTRIES},}${QUAY_REPO}" | tee -a "${GITHUB_ENV}"
|
||||||
|
|
||||||
|
- name: Create manifest list, push it and extract digest SHA
|
||||||
|
working-directory: ${{ runner.temp }}/digests
|
||||||
|
env:
|
||||||
|
BASE_IMAGE: "${{ matrix.base_image }}"
|
||||||
|
BASE_TAGS: "${{ env.BASE_TAGS }}"
|
||||||
|
CONTAINER_REGISTRIES: "${{ env.CONTAINER_REGISTRIES }}"
|
||||||
|
run: |
|
||||||
|
set +e
|
||||||
|
IFS=',' read -ra IMAGES <<< "${CONTAINER_REGISTRIES}"
|
||||||
|
for img in "${IMAGES[@]}"; do
|
||||||
|
echo "Creating manifest for $img:${BASE_TAGS}-${BASE_IMAGE}"
|
||||||
|
|
||||||
|
OUTPUT=$(docker buildx imagetools create \
|
||||||
|
-t "$img:${BASE_TAGS}-${BASE_IMAGE}" \
|
||||||
|
$(printf "$img:${BASE_TAGS}-${BASE_IMAGE}@sha256:%s " *) 2>&1)
|
||||||
|
STATUS=$?
|
||||||
|
|
||||||
|
if [ $STATUS -ne 0 ]; then
|
||||||
|
echo "Manifest creation failed for $img"
|
||||||
|
echo "$OUTPUT"
|
||||||
|
exit $STATUS
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Manifest created for $img"
|
||||||
|
echo "$OUTPUT"
|
||||||
|
done
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Extract digest SHA for subsequent steps
|
||||||
|
GET_DIGEST_SHA="$(echo "$OUTPUT" | grep -oE 'sha256:[a-f0-9]{64}' | tail -1)"
|
||||||
|
echo "DIGEST_SHA=${GET_DIGEST_SHA}" | tee -a "${GITHUB_ENV}"
|
||||||
|
|
||||||
|
# Attest container images
|
||||||
|
- name: Attest - docker.io - ${{ matrix.base_image }}
|
||||||
|
if: ${{ env.HAVE_DOCKERHUB_LOGIN == 'true' && env.DIGEST_SHA != ''}}
|
||||||
|
uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a # v3.0.0
|
||||||
|
with:
|
||||||
|
subject-name: ${{ vars.DOCKERHUB_REPO }}
|
||||||
|
subject-digest: ${{ env.DIGEST_SHA }}
|
||||||
|
push-to-registry: true
|
||||||
|
|
||||||
|
- name: Attest - ghcr.io - ${{ matrix.base_image }}
|
||||||
|
if: ${{ env.HAVE_GHCR_LOGIN == 'true' && env.DIGEST_SHA != ''}}
|
||||||
|
uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a # v3.0.0
|
||||||
|
with:
|
||||||
|
subject-name: ${{ vars.GHCR_REPO }}
|
||||||
|
subject-digest: ${{ env.DIGEST_SHA }}
|
||||||
|
push-to-registry: true
|
||||||
|
|
||||||
|
- name: Attest - quay.io - ${{ matrix.base_image }}
|
||||||
|
if: ${{ env.HAVE_QUAY_LOGIN == 'true' && env.DIGEST_SHA != ''}}
|
||||||
|
uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a # v3.0.0
|
||||||
|
with:
|
||||||
|
subject-name: ${{ vars.QUAY_REPO }}
|
||||||
|
subject-digest: ${{ env.DIGEST_SHA }}
|
||||||
|
push-to-registry: true
|
||||||
|
|||||||
4
.github/workflows/trivy.yml
vendored
4
.github/workflows/trivy.yml
vendored
@@ -29,7 +29,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 #v5.0.0
|
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 #v6.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
@@ -46,6 +46,6 @@ jobs:
|
|||||||
severity: CRITICAL,HIGH
|
severity: CRITICAL,HIGH
|
||||||
|
|
||||||
- name: Upload Trivy scan results to GitHub Security tab
|
- name: Upload Trivy scan results to GitHub Security tab
|
||||||
uses: github/codeql-action/upload-sarif@0499de31b99561a6d14a36a5f662c2a54f91beee # v4.31.2
|
uses: github/codeql-action/upload-sarif@fdbfb4d2750291e159f0156def62b853c2798ca2 # v4.31.5
|
||||||
with:
|
with:
|
||||||
sarif_file: 'trivy-results.sarif'
|
sarif_file: 'trivy-results.sarif'
|
||||||
|
|||||||
4
.github/workflows/typos.yml
vendored
4
.github/workflows/typos.yml
vendored
@@ -12,11 +12,11 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
# Checkout the repo
|
# Checkout the repo
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 #v5.0.0
|
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 #v6.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
# End Checkout the repo
|
# End Checkout the repo
|
||||||
|
|
||||||
# When this version is updated, do not forget to update this in `.pre-commit-config.yaml` too
|
# When this version is updated, do not forget to update this in `.pre-commit-config.yaml` too
|
||||||
- name: Spell Check Repo
|
- name: Spell Check Repo
|
||||||
uses: crate-ci/typos@07d900b8fa1097806b8adb6391b0d3e0ac2fdea7 # v1.39.0
|
uses: crate-ci/typos@2d0ce569feab1f8752f1dde43cc2f2aa53236e06 # v1.40.0
|
||||||
|
|||||||
4
.github/workflows/zizmor.yml
vendored
4
.github/workflows/zizmor.yml
vendored
@@ -16,12 +16,12 @@ jobs:
|
|||||||
security-events: write # To write the security report
|
security-events: write # To write the security report
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 #v5.0.0
|
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 #v6.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Run zizmor
|
- name: Run zizmor
|
||||||
uses: zizmorcore/zizmor-action@e673c3917a1aef3c65c972347ed84ccd013ecda4 # v0.2.0
|
uses: zizmorcore/zizmor-action@e639db99335bc9038abc0e066dfcd72e23d26fb4 # v0.3.0
|
||||||
with:
|
with:
|
||||||
# intentionally not scanning the entire repository,
|
# intentionally not scanning the entire repository,
|
||||||
# since it contains integration tests.
|
# since it contains integration tests.
|
||||||
|
|||||||
@@ -53,6 +53,6 @@ repos:
|
|||||||
- "cd docker && make"
|
- "cd docker && make"
|
||||||
# When this version is updated, do not forget to update this in `.github/workflows/typos.yaml` too
|
# When this version is updated, do not forget to update this in `.github/workflows/typos.yaml` too
|
||||||
- repo: https://github.com/crate-ci/typos
|
- repo: https://github.com/crate-ci/typos
|
||||||
rev: 07d900b8fa1097806b8adb6391b0d3e0ac2fdea7 # v1.39.0
|
rev: 2d0ce569feab1f8752f1dde43cc2f2aa53236e06 # v1.40.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: typos
|
- id: typos
|
||||||
|
|||||||
446
Cargo.lock
generated
446
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
25
Cargo.toml
25
Cargo.toml
@@ -57,7 +57,7 @@ macros = { path = "./macros" }
|
|||||||
# Logging
|
# Logging
|
||||||
log = "0.4.28"
|
log = "0.4.28"
|
||||||
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.43", features = ["log"] } # Needed to have lettre and webauthn-rs trace logging to work
|
||||||
|
|
||||||
# A `dotenv` implementation for Rust
|
# A `dotenv` implementation for Rust
|
||||||
dotenvy = { version = "0.15.7", default-features = false }
|
dotenvy = { version = "0.15.7", default-features = false }
|
||||||
@@ -87,8 +87,9 @@ serde = { version = "1.0.228", features = ["derive"] }
|
|||||||
serde_json = "1.0.145"
|
serde_json = "1.0.145"
|
||||||
|
|
||||||
# A safe, extensible ORM and Query builder
|
# A safe, extensible ORM and Query builder
|
||||||
diesel = { version = "2.3.3", features = ["chrono", "r2d2", "numeric"] }
|
# Currently pinned diesel to v2.3.3 as newer version break MySQL/MariaDB compatibility
|
||||||
diesel_migrations = "2.3.0"
|
diesel = { version = "=2.3.3", features = ["chrono", "r2d2", "numeric"] }
|
||||||
|
diesel_migrations = "2.3.1"
|
||||||
|
|
||||||
derive_more = { version = "2.0.1", 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"
|
||||||
@@ -116,7 +117,7 @@ job_scheduler_ng = "2.4.0"
|
|||||||
data-encoding = "2.9.0"
|
data-encoding = "2.9.0"
|
||||||
|
|
||||||
# JWT library
|
# JWT library
|
||||||
jsonwebtoken = "9.3.1"
|
jsonwebtoken = { version = "10.2.0", features = ["use_pem", "rust_crypto"], default-features = false }
|
||||||
|
|
||||||
# TOTP library
|
# TOTP library
|
||||||
totp-lite = "2.0.1"
|
totp-lite = "2.0.1"
|
||||||
@@ -147,10 +148,10 @@ reqwest = { version = "0.12.24", features = ["rustls-tls", "rustls-tls-native-ro
|
|||||||
hickory-resolver = "0.25.2"
|
hickory-resolver = "0.25.2"
|
||||||
|
|
||||||
# Favicon extraction libraries
|
# Favicon extraction libraries
|
||||||
html5gum = "0.8.0"
|
html5gum = "0.8.1"
|
||||||
regex = { version = "1.12.2", features = ["std", "perf", "unicode-perl"], default-features = false }
|
regex = { version = "1.12.2", features = ["std", "perf", "unicode-perl"], default-features = false }
|
||||||
data-url = "0.3.2"
|
data-url = "0.3.2"
|
||||||
bytes = "1.10.1"
|
bytes = "1.11.0"
|
||||||
svg-hush = "0.9.5"
|
svg-hush = "0.9.5"
|
||||||
|
|
||||||
# Cache function results (Used for version check and favicon fetching)
|
# Cache function results (Used for version check and favicon fetching)
|
||||||
@@ -167,8 +168,8 @@ openssl = "0.10.75"
|
|||||||
pico-args = "0.5.0"
|
pico-args = "0.5.0"
|
||||||
|
|
||||||
# Macro ident concatenation
|
# Macro ident concatenation
|
||||||
pastey = "0.1.1"
|
pastey = "0.2.0"
|
||||||
governor = "0.10.1"
|
governor = "0.10.2"
|
||||||
|
|
||||||
# OIDC for SSO
|
# OIDC for SSO
|
||||||
openidconnect = { version = "4.0.1", features = ["reqwest", "native-tls"] }
|
openidconnect = { version = "4.0.1", features = ["reqwest", "native-tls"] }
|
||||||
@@ -193,14 +194,14 @@ rpassword = "7.4.0"
|
|||||||
grass_compiler = { version = "0.13.4", default-features = false }
|
grass_compiler = { version = "0.13.4", default-features = false }
|
||||||
|
|
||||||
# File are accessed through Apache OpenDAL
|
# File are accessed through Apache OpenDAL
|
||||||
opendal = { version = "0.54.1", features = ["services-fs"], default-features = false }
|
opendal = { version = "0.55.0", features = ["services-fs"], default-features = false }
|
||||||
|
|
||||||
# For retrieving AWS credentials, including temporary SSO credentials
|
# For retrieving AWS credentials, including temporary SSO credentials
|
||||||
anyhow = { version = "1.0.100", optional = true }
|
anyhow = { version = "1.0.100", optional = true }
|
||||||
aws-config = { version = "1.8.10", features = ["behavior-version-latest", "rt-tokio", "credentials-process", "sso"], default-features = false, optional = true }
|
aws-config = { version = "1.8.11", features = ["behavior-version-latest", "rt-tokio", "credentials-process", "sso"], default-features = false, optional = true }
|
||||||
aws-credential-types = { version = "1.2.9", optional = true }
|
aws-credential-types = { version = "1.2.10", optional = true }
|
||||||
aws-smithy-runtime-api = { version = "1.9.2", optional = true }
|
aws-smithy-runtime-api = { version = "1.9.2", optional = true }
|
||||||
http = { version = "1.3.1", optional = true }
|
http = { version = "1.4.0", optional = true }
|
||||||
reqsign = { version = "0.16.5", optional = true }
|
reqsign = { version = "0.16.5", optional = true }
|
||||||
|
|
||||||
# Strip debuginfo from the release builds
|
# Strip debuginfo from the release builds
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ vault_image_digest: "sha256:50662dccf4908ac2128cd44981c52fcb4e3e8dd56f21823c8d5e
|
|||||||
# 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:add602d55daca18914838a78221f6bbe4284114b452c86a48f96d59aeb00f5c6"
|
xx_image_digest: "sha256:add602d55daca18914838a78221f6bbe4284114b452c86a48f96d59aeb00f5c6"
|
||||||
rust_version: 1.91.0 # Rust version to be used
|
rust_version: 1.91.1 # Rust version to be used
|
||||||
debian_version: trixie # Debian release name to be used
|
debian_version: trixie # Debian release name to be used
|
||||||
alpine_version: "3.22" # Alpine version to be used
|
alpine_version: "3.22" # 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
|
||||||
@@ -17,7 +17,6 @@ build_stage_image:
|
|||||||
platform: "$BUILDPLATFORM"
|
platform: "$BUILDPLATFORM"
|
||||||
alpine:
|
alpine:
|
||||||
image: "build_${TARGETARCH}${TARGETVARIANT}"
|
image: "build_${TARGETARCH}${TARGETVARIANT}"
|
||||||
platform: "linux/amd64" # The Alpine build images only have linux/amd64 images
|
|
||||||
arch_image:
|
arch_image:
|
||||||
amd64: "ghcr.io/blackdex/rust-musl:x86_64-musl-stable-{{rust_version}}"
|
amd64: "ghcr.io/blackdex/rust-musl:x86_64-musl-stable-{{rust_version}}"
|
||||||
arm64: "ghcr.io/blackdex/rust-musl:aarch64-musl-stable-{{rust_version}}"
|
arm64: "ghcr.io/blackdex/rust-musl:aarch64-musl-stable-{{rust_version}}"
|
||||||
|
|||||||
@@ -30,16 +30,16 @@
|
|||||||
FROM --platform=linux/amd64 docker.io/vaultwarden/web-vault@sha256:50662dccf4908ac2128cd44981c52fcb4e3e8dd56f21823c8d5e91267ff741fa AS vault
|
FROM --platform=linux/amd64 docker.io/vaultwarden/web-vault@sha256:50662dccf4908ac2128cd44981c52fcb4e3e8dd56f21823c8d5e91267ff741fa 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 linux/arm64
|
||||||
## And for Alpine we define all build images here, they will only be loaded when actually used
|
## And for Alpine we define all build images here, they will only be loaded when actually used
|
||||||
FROM --platform=linux/amd64 ghcr.io/blackdex/rust-musl:x86_64-musl-stable-1.91.0 AS build_amd64
|
FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:x86_64-musl-stable-1.91.1 AS build_amd64
|
||||||
FROM --platform=linux/amd64 ghcr.io/blackdex/rust-musl:aarch64-musl-stable-1.91.0 AS build_arm64
|
FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:aarch64-musl-stable-1.91.1 AS build_arm64
|
||||||
FROM --platform=linux/amd64 ghcr.io/blackdex/rust-musl:armv7-musleabihf-stable-1.91.0 AS build_armv7
|
FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:armv7-musleabihf-stable-1.91.1 AS build_armv7
|
||||||
FROM --platform=linux/amd64 ghcr.io/blackdex/rust-musl:arm-musleabi-stable-1.91.0 AS build_armv6
|
FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:arm-musleabi-stable-1.91.1 AS build_armv6
|
||||||
|
|
||||||
########################## BUILD IMAGE ##########################
|
########################## BUILD IMAGE ##########################
|
||||||
# hadolint ignore=DL3006
|
# hadolint ignore=DL3006
|
||||||
FROM --platform=linux/amd64 build_${TARGETARCH}${TARGETVARIANT} AS build
|
FROM --platform=$BUILDPLATFORM build_${TARGETARCH}${TARGETVARIANT} AS build
|
||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
ARG TARGETVARIANT
|
ARG TARGETVARIANT
|
||||||
ARG TARGETPLATFORM
|
ARG TARGETPLATFORM
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ FROM --platform=linux/amd64 docker.io/tonistiigi/xx@sha256:add602d55daca18914838
|
|||||||
|
|
||||||
########################## BUILD IMAGE ##########################
|
########################## BUILD IMAGE ##########################
|
||||||
# hadolint ignore=DL3006
|
# hadolint ignore=DL3006
|
||||||
FROM --platform=$BUILDPLATFORM docker.io/library/rust:1.91.0-slim-trixie AS build
|
FROM --platform=$BUILDPLATFORM docker.io/library/rust:1.91.1-slim-trixie AS build
|
||||||
COPY --from=xx / /
|
COPY --from=xx / /
|
||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
ARG TARGETVARIANT
|
ARG TARGETVARIANT
|
||||||
|
|||||||
@@ -36,16 +36,16 @@ FROM --platform=linux/amd64 docker.io/vaultwarden/web-vault@{{ vault_image_diges
|
|||||||
FROM --platform=linux/amd64 docker.io/tonistiigi/xx@{{ xx_image_digest }} AS xx
|
FROM --platform=linux/amd64 docker.io/tonistiigi/xx@{{ xx_image_digest }} AS xx
|
||||||
{% elif base == "alpine" %}
|
{% elif base == "alpine" %}
|
||||||
########################## 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 linux/arm64
|
||||||
## And for Alpine we define all build images here, they will only be loaded when actually used
|
## And for Alpine we define all build images here, they will only be loaded when actually used
|
||||||
{% for arch in build_stage_image[base].arch_image %}
|
{% for arch in build_stage_image[base].arch_image %}
|
||||||
FROM --platform={{ build_stage_image[base].platform }} {{ build_stage_image[base].arch_image[arch] }} AS build_{{ arch }}
|
FROM --platform=$BUILDPLATFORM {{ build_stage_image[base].arch_image[arch] }} AS build_{{ arch }}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
########################## BUILD IMAGE ##########################
|
########################## BUILD IMAGE ##########################
|
||||||
# hadolint ignore=DL3006
|
# hadolint ignore=DL3006
|
||||||
FROM --platform={{ build_stage_image[base].platform }} {{ build_stage_image[base].image }} AS build
|
FROM --platform=$BUILDPLATFORM {{ build_stage_image[base].image }} AS build
|
||||||
{% if base == "debian" %}
|
{% if base == "debian" %}
|
||||||
COPY --from=xx / /
|
COPY --from=xx / /
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ path = "src/lib.rs"
|
|||||||
proc-macro = true
|
proc-macro = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
quote = "1.0.41"
|
quote = "1.0.42"
|
||||||
syn = "2.0.108"
|
syn = "2.0.110"
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|||||||
@@ -1,2 +1,15 @@
|
|||||||
ALTER TABLE sso_users DROP FOREIGN KEY `sso_users_ibfk_1`;
|
-- Dynamically create DROP FOREIGN KEY
|
||||||
|
-- Some versions of MySQL or MariaDB might fail if the key doesn't exists
|
||||||
|
-- This checks if the key exists, and if so, will drop it.
|
||||||
|
SET @drop_sso_fk = IF((SELECT true FROM information_schema.TABLE_CONSTRAINTS WHERE
|
||||||
|
CONSTRAINT_SCHEMA = DATABASE() AND
|
||||||
|
TABLE_NAME = 'sso_users' AND
|
||||||
|
CONSTRAINT_NAME = 'sso_users_ibfk_1' AND
|
||||||
|
CONSTRAINT_TYPE = 'FOREIGN KEY') = true,
|
||||||
|
'ALTER TABLE sso_users DROP FOREIGN KEY sso_users_ibfk_1',
|
||||||
|
'SELECT 1');
|
||||||
|
PREPARE stmt FROM @drop_sso_fk;
|
||||||
|
EXECUTE stmt;
|
||||||
|
DEALLOCATE PREPARE stmt;
|
||||||
|
|
||||||
ALTER TABLE sso_users ADD FOREIGN KEY(user_uuid) REFERENCES users(uuid) ON UPDATE CASCADE ON DELETE CASCADE;
|
ALTER TABLE sso_users ADD FOREIGN KEY(user_uuid) REFERENCES users(uuid) ON UPDATE CASCADE ON DELETE CASCADE;
|
||||||
|
|||||||
@@ -97,9 +97,9 @@ npx playwright codegen "http://127.0.0.1:8003"
|
|||||||
|
|
||||||
## Override web-vault
|
## Override web-vault
|
||||||
|
|
||||||
It is possible to change the `web-vault` used by referencing a different `bw_web_builds` commit.
|
It is possible to change the `web-vault` used by referencing a different `vw_web_builds` commit.
|
||||||
|
|
||||||
Simplest is to set and uncomment `PW_WV_REPO_URL` and `PW_WV_COMMIT_HASH` in the `test.env`.
|
Simplest is to set and uncomment `PW_VW_REPO_URL` and `PW_VW_COMMIT_HASH` in the `test.env`.
|
||||||
Ensure that the image is built with:
|
Ensure that the image is built with:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -112,6 +112,8 @@ You can check the result running:
|
|||||||
DOCKER_BUILDKIT=1 docker compose --profile playwright --env-file test.env up Vaultwarden
|
DOCKER_BUILDKIT=1 docker compose --profile playwright --env-file test.env up Vaultwarden
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Then check `http://127.0.0.1:8003/admin/diagnostics` with `admin`.
|
||||||
|
|
||||||
# OpenID Connect test setup
|
# OpenID Connect test setup
|
||||||
|
|
||||||
Additionally this `docker-compose` template allows to run locally Vaultwarden,
|
Additionally this `docker-compose` template allows to run locally Vaultwarden,
|
||||||
|
|||||||
@@ -6,18 +6,19 @@ echo $COMMIT_HASH
|
|||||||
if [[ ! -z "$REPO_URL" ]] && [[ ! -z "$COMMIT_HASH" ]] ; then
|
if [[ ! -z "$REPO_URL" ]] && [[ ! -z "$COMMIT_HASH" ]] ; then
|
||||||
rm -rf /web-vault
|
rm -rf /web-vault
|
||||||
|
|
||||||
mkdir bw_web_builds;
|
mkdir -p vw_web_builds;
|
||||||
cd bw_web_builds;
|
cd vw_web_builds;
|
||||||
|
|
||||||
git -c init.defaultBranch=main init
|
git -c init.defaultBranch=main init
|
||||||
git remote add origin "$REPO_URL"
|
git remote add origin "$REPO_URL"
|
||||||
git fetch --depth 1 origin "$COMMIT_HASH"
|
git fetch --depth 1 origin "$COMMIT_HASH"
|
||||||
git -c advice.detachedHead=false checkout FETCH_HEAD
|
git -c advice.detachedHead=false checkout FETCH_HEAD
|
||||||
|
|
||||||
export VAULT_VERSION=$(cat Dockerfile | grep "ARG VAULT_VERSION" | cut -d "=" -f2)
|
npm ci --ignore-scripts
|
||||||
./scripts/checkout_web_vault.sh
|
|
||||||
./scripts/build_web_vault.sh
|
|
||||||
printf '{"version":"%s"}' "$COMMIT_HASH" > ./web-vault/apps/web/build/vw-version.json
|
|
||||||
|
|
||||||
mv ./web-vault/apps/web/build /web-vault
|
cd apps/web
|
||||||
|
npm run dist:oss:selfhost
|
||||||
|
printf '{"version":"%s"}' "$COMMIT_HASH" > build/vw-version.json
|
||||||
|
|
||||||
|
mv build /web-vault
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -18,10 +18,11 @@ services:
|
|||||||
context: compose/warden
|
context: compose/warden
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
args:
|
args:
|
||||||
REPO_URL: ${PW_WV_REPO_URL:-}
|
REPO_URL: ${PW_VW_REPO_URL:-}
|
||||||
COMMIT_HASH: ${PW_WV_COMMIT_HASH:-}
|
COMMIT_HASH: ${PW_VW_COMMIT_HASH:-}
|
||||||
env_file: ${DC_ENV_FILE:-.env}
|
env_file: ${DC_ENV_FILE:-.env}
|
||||||
environment:
|
environment:
|
||||||
|
- ADMIN_TOKEN
|
||||||
- DATABASE_URL
|
- DATABASE_URL
|
||||||
- I_REALLY_WANT_VOLATILE_STORAGE
|
- I_REALLY_WANT_VOLATILE_STORAGE
|
||||||
- LOG_LEVEL
|
- LOG_LEVEL
|
||||||
|
|||||||
@@ -221,9 +221,13 @@ export async function restartVault(page: Page, testInfo: TestInfo, env, resetDB:
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function checkNotification(page: Page, hasText: string) {
|
export async function checkNotification(page: Page, hasText: string) {
|
||||||
await expect(page.locator('bit-toast').filter({ hasText })).toBeVisible();
|
await expect(page.locator('bit-toast', { hasText })).toBeVisible();
|
||||||
await page.locator('bit-toast').filter({ hasText }).getByRole('button').click();
|
try {
|
||||||
await expect(page.locator('bit-toast').filter({ hasText })).toHaveCount(0);
|
await page.locator('bit-toast', { hasText }).getByRole('button', { name: 'Close' }).click({force: true, timeout: 10_000});
|
||||||
|
} catch (error) {
|
||||||
|
console.log(`Closing notification failed but it should now be invisible (${error})`);
|
||||||
|
}
|
||||||
|
await expect(page.locator('bit-toast', { hasText })).toHaveCount(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function cleanLanding(page: Page) {
|
export async function cleanLanding(page: Page) {
|
||||||
@@ -244,3 +248,15 @@ export async function logout(test: Test, page: Page, user: { name: string }) {
|
|||||||
await expect(page.getByRole('heading', { name: 'Log in' })).toBeVisible();
|
await expect(page.getByRole('heading', { name: 'Log in' })).toBeVisible();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function ignoreExtension(page: Page) {
|
||||||
|
await page.waitForLoadState('domcontentloaded');
|
||||||
|
|
||||||
|
try {
|
||||||
|
await page.getByRole('button', { name: 'Add it later' }).click({timeout: 5_000});
|
||||||
|
await page.getByRole('link', { name: 'Skip to web app' }).click();
|
||||||
|
} catch (error) {
|
||||||
|
console.log('Extension setup not visible. Continuing');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
328
playwright/package-lock.json
generated
328
playwright/package-lock.json
generated
@@ -9,15 +9,15 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"mysql2": "3.15.0",
|
"mysql2": "3.15.3",
|
||||||
"otpauth": "9.4.1",
|
"otpauth": "9.4.1",
|
||||||
"pg": "8.16.3"
|
"pg": "8.16.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@playwright/test": "1.55.1",
|
"@playwright/test": "1.56.1",
|
||||||
"dotenv": "17.2.2",
|
"dotenv": "17.2.3",
|
||||||
"dotenv-expand": "12.0.3",
|
"dotenv-expand": "12.0.3",
|
||||||
"maildev": "npm:@timshel_npm/maildev@^3.2.3"
|
"maildev": "npm:@timshel_npm/maildev@3.2.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@asamuzakjp/css-color": {
|
"node_modules/@asamuzakjp/css-color": {
|
||||||
@@ -34,16 +34,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@asamuzakjp/dom-selector": {
|
"node_modules/@asamuzakjp/dom-selector": {
|
||||||
"version": "6.5.6",
|
"version": "6.7.3",
|
||||||
"resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-6.5.6.tgz",
|
"resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-6.7.3.tgz",
|
||||||
"integrity": "sha512-Mj3Hu9ymlsERd7WOsUKNUZnJYL4IZ/I9wVVYgtvOsWYiEFbkQ4G7VRIh2USxTVW4BBDIsLG+gBUgqOqf2Kvqow==",
|
"integrity": "sha512-kiGFeY+Hxf5KbPpjRLf+ffWbkos1aGo8MBfd91oxS3O57RgU3XhZrt/6UzoVF9VMpWbC3v87SRc9jxGrc9qHtQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@asamuzakjp/nwsapi": "^2.3.9",
|
"@asamuzakjp/nwsapi": "^2.3.9",
|
||||||
"bidi-js": "^1.0.3",
|
"bidi-js": "^1.0.3",
|
||||||
"css-tree": "^3.1.0",
|
"css-tree": "^3.1.0",
|
||||||
"is-potential-custom-element-name": "^1.0.1",
|
"is-potential-custom-element-name": "^1.0.1",
|
||||||
"lru-cache": "^11.2.1"
|
"lru-cache": "^11.2.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@asamuzakjp/nwsapi": {
|
"node_modules/@asamuzakjp/nwsapi": {
|
||||||
@@ -144,9 +144,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@csstools/css-syntax-patches-for-csstree": {
|
"node_modules/@csstools/css-syntax-patches-for-csstree": {
|
||||||
"version": "1.0.14",
|
"version": "1.0.15",
|
||||||
"resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.14.tgz",
|
"resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.15.tgz",
|
||||||
"integrity": "sha512-zSlIxa20WvMojjpCSy8WrNpcZ61RqfTfX3XTaOeVlGJrt/8HF3YbzgFZa01yTbT4GWQLwfTcC3EB8i3XnB647Q==",
|
"integrity": "sha512-q0p6zkVq2lJnmzZVPR33doA51G7YOja+FBvRdp5ISIthL0MtFCgYHHhR563z9WFGxcOn0WfjSkPDJ5Qig3H3Sw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -160,9 +160,6 @@
|
|||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"postcss": "^8.4"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@csstools/css-tokenizer": {
|
"node_modules/@csstools/css-tokenizer": {
|
||||||
@@ -196,12 +193,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@playwright/test": {
|
"node_modules/@playwright/test": {
|
||||||
"version": "1.55.1",
|
"version": "1.56.1",
|
||||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.55.1.tgz",
|
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.56.1.tgz",
|
||||||
"integrity": "sha512-IVAh/nOJaw6W9g+RJVlIQJ6gSiER+ae6mKQ5CX1bERzQgbC1VSeBlwdvczT7pxb0GWiyrxH4TGKbMfDb4Sq/ig==",
|
"integrity": "sha512-vSMYtL/zOcFpvJCW71Q/OEGQb7KYBPAdKh35WNSkaZA75JlAO8ED8UN6GUNTm3drWomcbcqRPFqQbLae8yBTdg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"playwright": "1.55.1"
|
"playwright": "1.56.1"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"playwright": "cli.js"
|
"playwright": "cli.js"
|
||||||
@@ -249,12 +246,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "24.5.2",
|
"version": "24.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.5.2.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.2.1.tgz",
|
||||||
"integrity": "sha512-FYxk1I7wPv3K2XBaoyH2cTnocQEu8AOZ60hPbsyukMPLv5/5qr7V1i8PLHdl6Zf87I+xZXFvPCXYjiTFq+YSDQ==",
|
"integrity": "sha512-DRh5K+ka5eJic8CjH7td8QpYEV6Zo10gfRkjHCO3weqZHWDtAaSTFtl4+VMqOJ4N5jcuhZ9/l+yy8rVgw7BQeQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~7.12.0"
|
"undici-types": "~7.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/trusted-types": {
|
"node_modules/@types/trusted-types": {
|
||||||
@@ -363,9 +360,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/body-parser/node_modules/debug": {
|
"node_modules/body-parser/node_modules/debug": {
|
||||||
"version": "4.4.3",
|
"version": "4.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
|
||||||
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
|
"integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ms": "^2.1.3"
|
"ms": "^2.1.3"
|
||||||
@@ -637,9 +634,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/dompurify": {
|
"node_modules/dompurify": {
|
||||||
"version": "3.2.7",
|
"version": "3.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.7.tgz",
|
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.0.tgz",
|
||||||
"integrity": "sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw==",
|
"integrity": "sha512-r+f6MYR1gGN1eJv0TVQbhA7if/U7P87cdPl3HN5rikqaBSBxLiCb/b9O+2eG0cxz0ghyU+mU1QkbsOwERMYlWQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@types/trusted-types": "^2.0.7"
|
"@types/trusted-types": "^2.0.7"
|
||||||
@@ -660,9 +657,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/dotenv": {
|
"node_modules/dotenv": {
|
||||||
"version": "17.2.2",
|
"version": "17.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz",
|
||||||
"integrity": "sha512-Sf2LSQP+bOlhKWWyhFsn0UsfdK/kCWRv1iuA2gXAwt3dyNabr6QSj00I2V10pidqz69soatm9ZwZvpQMTIOd5Q==",
|
"integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
@@ -952,9 +949,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/express/node_modules/debug": {
|
"node_modules/express/node_modules/debug": {
|
||||||
"version": "4.4.3",
|
"version": "4.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
|
||||||
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
|
"integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ms": "^2.1.3"
|
"ms": "^2.1.3"
|
||||||
@@ -992,9 +989,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/finalhandler/node_modules/debug": {
|
"node_modules/finalhandler/node_modules/debug": {
|
||||||
"version": "4.4.3",
|
"version": "4.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
|
||||||
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
|
"integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ms": "^2.1.3"
|
"ms": "^2.1.3"
|
||||||
@@ -1340,20 +1337,20 @@
|
|||||||
"integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g=="
|
"integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g=="
|
||||||
},
|
},
|
||||||
"node_modules/jsdom": {
|
"node_modules/jsdom": {
|
||||||
"version": "27.0.0",
|
"version": "27.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-27.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-27.0.1.tgz",
|
||||||
"integrity": "sha512-lIHeR1qlIRrIN5VMccd8tI2Sgw6ieYXSVktcSHaNe3Z5nE/tcPQYQWOq00wxMvYOsz+73eAkNenVvmPC6bba9A==",
|
"integrity": "sha512-SNSQteBL1IlV2zqhwwolaG9CwhIhTvVHWg3kTss/cLE7H/X4644mtPQqYvCfsSrGQWt9hSZcgOXX8bOZaMN+kA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@asamuzakjp/dom-selector": "^6.5.4",
|
"@asamuzakjp/dom-selector": "^6.7.2",
|
||||||
"cssstyle": "^5.3.0",
|
"cssstyle": "^5.3.1",
|
||||||
"data-urls": "^6.0.0",
|
"data-urls": "^6.0.0",
|
||||||
"decimal.js": "^10.5.0",
|
"decimal.js": "^10.6.0",
|
||||||
"html-encoding-sniffer": "^4.0.0",
|
"html-encoding-sniffer": "^4.0.0",
|
||||||
"http-proxy-agent": "^7.0.2",
|
"http-proxy-agent": "^7.0.2",
|
||||||
"https-proxy-agent": "^7.0.6",
|
"https-proxy-agent": "^7.0.6",
|
||||||
"is-potential-custom-element-name": "^1.0.1",
|
"is-potential-custom-element-name": "^1.0.1",
|
||||||
"parse5": "^7.3.0",
|
"parse5": "^8.0.0",
|
||||||
"rrweb-cssom": "^0.8.0",
|
"rrweb-cssom": "^0.8.0",
|
||||||
"saxes": "^6.0.0",
|
"saxes": "^6.0.0",
|
||||||
"symbol-tree": "^3.2.4",
|
"symbol-tree": "^3.2.4",
|
||||||
@@ -1362,8 +1359,8 @@
|
|||||||
"webidl-conversions": "^8.0.0",
|
"webidl-conversions": "^8.0.0",
|
||||||
"whatwg-encoding": "^3.1.1",
|
"whatwg-encoding": "^3.1.1",
|
||||||
"whatwg-mimetype": "^4.0.0",
|
"whatwg-mimetype": "^4.0.0",
|
||||||
"whatwg-url": "^15.0.0",
|
"whatwg-url": "^15.1.0",
|
||||||
"ws": "^8.18.2",
|
"ws": "^8.18.3",
|
||||||
"xml-name-validator": "^5.0.0"
|
"xml-name-validator": "^5.0.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -1426,9 +1423,9 @@
|
|||||||
"integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="
|
"integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="
|
||||||
},
|
},
|
||||||
"node_modules/lru-cache": {
|
"node_modules/lru-cache": {
|
||||||
"version": "11.2.1",
|
"version": "11.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz",
|
||||||
"integrity": "sha512-r8LA6i4LP4EeWOhqBaZZjDWwehd1xUJPCJd9Sv300H0ZmcUER4+JPh7bqqZeqs1o5pgtgvXm+d9UGrB5zZGDiQ==",
|
"integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "20 || >=22"
|
"node": "20 || >=22"
|
||||||
@@ -1450,9 +1447,9 @@
|
|||||||
},
|
},
|
||||||
"node_modules/maildev": {
|
"node_modules/maildev": {
|
||||||
"name": "@timshel_npm/maildev",
|
"name": "@timshel_npm/maildev",
|
||||||
"version": "3.2.3",
|
"version": "3.2.5",
|
||||||
"resolved": "https://registry.npmjs.org/@timshel_npm/maildev/-/maildev-3.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/@timshel_npm/maildev/-/maildev-3.2.5.tgz",
|
||||||
"integrity": "sha512-CNxMz4Obw7nL8MZbx4y1YUFeqqAQk+Qwm51tcBV5lRBotMlXKeYhuEcayb1v66nUwq832bUfKF4hyQpJixFZrw==",
|
"integrity": "sha512-suWQu2s2kmO+MXtNJYW9peklznhd+aorIUb4tSNrfaKoEJjDa3vLXTvWf+3cb67o4Yv4Z6nPeKdMTCDZVn/Nyw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/mailparser": "3.4.6",
|
"@types/mailparser": "3.4.6",
|
||||||
@@ -1461,13 +1458,13 @@
|
|||||||
"commander": "14.0.1",
|
"commander": "14.0.1",
|
||||||
"compression": "1.8.1",
|
"compression": "1.8.1",
|
||||||
"cors": "2.8.5",
|
"cors": "2.8.5",
|
||||||
"dompurify": "3.2.7",
|
"dompurify": "3.3.0",
|
||||||
"express": "5.1.0",
|
"express": "5.1.0",
|
||||||
"jsdom": "27.0.0",
|
"jsdom": "27.0.1",
|
||||||
"mailparser": "3.7.4",
|
"mailparser": "3.7.5",
|
||||||
"mime": "4.1.0",
|
"mime": "4.1.0",
|
||||||
"nodemailer": "7.0.6",
|
"nodemailer": "7.0.9",
|
||||||
"smtp-server": "3.14.0",
|
"smtp-server": "3.15.0",
|
||||||
"socket.io": "4.8.1",
|
"socket.io": "4.8.1",
|
||||||
"wildstring": "1.0.9"
|
"wildstring": "1.0.9"
|
||||||
},
|
},
|
||||||
@@ -1479,36 +1476,44 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/mailparser": {
|
"node_modules/mailparser": {
|
||||||
"version": "3.7.4",
|
"version": "3.7.5",
|
||||||
"resolved": "https://registry.npmjs.org/mailparser/-/mailparser-3.7.4.tgz",
|
"resolved": "https://registry.npmjs.org/mailparser/-/mailparser-3.7.5.tgz",
|
||||||
"integrity": "sha512-Beh4yyR4jLq3CZZ32asajByrXnW8dLyKCAQD3WvtTiBnMtFWhxO+wa93F6sJNjDmfjxXs4NRNjw3XAGLqZR3Vg==",
|
"integrity": "sha512-o59RgZC+4SyCOn4xRH1mtRiZ1PbEmi6si6Ufnd3tbX/V9zmZN1qcqu8xbXY62H6CwIclOT3ppm5u/wV2nujn4g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"encoding-japanese": "2.2.0",
|
"encoding-japanese": "2.2.0",
|
||||||
"he": "1.2.0",
|
"he": "1.2.0",
|
||||||
"html-to-text": "9.0.5",
|
"html-to-text": "9.0.5",
|
||||||
"iconv-lite": "0.6.3",
|
"iconv-lite": "0.7.0",
|
||||||
"libmime": "5.3.7",
|
"libmime": "5.3.7",
|
||||||
"linkify-it": "5.0.0",
|
"linkify-it": "5.0.0",
|
||||||
"mailsplit": "5.4.5",
|
"mailsplit": "5.4.6",
|
||||||
"nodemailer": "7.0.4",
|
"nodemailer": "7.0.9",
|
||||||
"punycode.js": "2.3.1",
|
"punycode.js": "2.3.1",
|
||||||
"tlds": "1.259.0"
|
"tlds": "1.260.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/mailparser/node_modules/nodemailer": {
|
"node_modules/mailparser/node_modules/iconv-lite": {
|
||||||
"version": "7.0.4",
|
"version": "0.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz",
|
||||||
"integrity": "sha512-9O00Vh89/Ld2EcVCqJ/etd7u20UhME0f/NToPfArwPEe1Don1zy4mAIz6ariRr7mJ2RDxtaDzN0WJVdVXPtZaw==",
|
"integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||||
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.0.0"
|
"node": ">=0.10.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/express"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/mailsplit": {
|
"node_modules/mailsplit": {
|
||||||
"version": "5.4.5",
|
"version": "5.4.6",
|
||||||
"resolved": "https://registry.npmjs.org/mailsplit/-/mailsplit-5.4.5.tgz",
|
"resolved": "https://registry.npmjs.org/mailsplit/-/mailsplit-5.4.6.tgz",
|
||||||
"integrity": "sha512-oMfhmvclR689IIaQmIcR5nODnZRRVwAKtqFT407TIvmhX2OLUBnshUTcxzQBt3+96sZVDud9NfSe1NxAkUNXEQ==",
|
"integrity": "sha512-M+cqmzaPG/mEiCDmqQUz8L177JZLZmXAUpq38owtpq2xlXlTSw+kntnxRt2xsxVFFV6+T8Mj/U0l5s7s6e0rNw==",
|
||||||
|
"deprecated": "This package has been renamed to @zone-eu/mailsplit. Please update your dependencies.",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"libbase64": "1.3.0",
|
"libbase64": "1.3.0",
|
||||||
@@ -1595,9 +1600,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/mysql2": {
|
"node_modules/mysql2": {
|
||||||
"version": "3.15.0",
|
"version": "3.15.3",
|
||||||
"resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.15.0.tgz",
|
"resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.15.3.tgz",
|
||||||
"integrity": "sha512-tT6pomf5Z/I7Jzxu8sScgrYBMK9bUFWd7Kbo6Fs1L0M13OOIJ/ZobGKS3Z7tQ8Re4lj+LnLXIQVZZxa3fhYKzA==",
|
"integrity": "sha512-FBrGau0IXmuqg4haEZRBfHNWB5mUARw6hNwPDXXGg0XzVJ50mr/9hb267lvpVMnhZ1FON3qNd4Xfcez1rbFwSg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"aws-ssl-profiles": "^1.1.1",
|
"aws-ssl-profiles": "^1.1.1",
|
||||||
"denque": "^2.1.0",
|
"denque": "^2.1.0",
|
||||||
@@ -1647,25 +1652,6 @@
|
|||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/nanoid": {
|
|
||||||
"version": "3.3.11",
|
|
||||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
|
|
||||||
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
|
|
||||||
"dev": true,
|
|
||||||
"funding": [
|
|
||||||
{
|
|
||||||
"type": "github",
|
|
||||||
"url": "https://github.com/sponsors/ai"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
|
||||||
"nanoid": "bin/nanoid.cjs"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/negotiator": {
|
"node_modules/negotiator": {
|
||||||
"version": "0.6.4",
|
"version": "0.6.4",
|
||||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz",
|
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz",
|
||||||
@@ -1676,9 +1662,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/nodemailer": {
|
"node_modules/nodemailer": {
|
||||||
"version": "7.0.6",
|
"version": "7.0.9",
|
||||||
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.9.tgz",
|
||||||
"integrity": "sha512-F44uVzgwo49xboqbFgBGkRaiMgtoBrBEWCVincJPK9+S9Adkzt/wXCLKbf7dxucmxfTI5gHGB+bEmdyzN6QKjw==",
|
"integrity": "sha512-9/Qm0qXIByEP8lEV2qOqcAW7bRpL8CR9jcTwk3NBnHJNmP9fIJ86g2fgmIXqHY+nj55ZEMwWqYAT2QTDpRUYiQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.0.0"
|
"node": ">=6.0.0"
|
||||||
@@ -1747,9 +1733,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/parse5": {
|
"node_modules/parse5": {
|
||||||
"version": "7.3.0",
|
"version": "8.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.0.tgz",
|
||||||
"integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==",
|
"integrity": "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"entities": "^6.0.0"
|
"entities": "^6.0.0"
|
||||||
@@ -1793,13 +1779,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/path-to-regexp": {
|
"node_modules/path-to-regexp": {
|
||||||
"version": "8.3.0",
|
"version": "8.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz",
|
||||||
"integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==",
|
"integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"funding": {
|
"engines": {
|
||||||
"type": "opencollective",
|
"node": ">=16"
|
||||||
"url": "https://opencollective.com/express"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/peberminta": {
|
"node_modules/peberminta": {
|
||||||
@@ -1892,20 +1877,13 @@
|
|||||||
"split2": "^4.1.0"
|
"split2": "^4.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/picocolors": {
|
|
||||||
"version": "1.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
|
||||||
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
|
||||||
"dev": true,
|
|
||||||
"peer": true
|
|
||||||
},
|
|
||||||
"node_modules/playwright": {
|
"node_modules/playwright": {
|
||||||
"version": "1.55.1",
|
"version": "1.56.1",
|
||||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.55.1.tgz",
|
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.56.1.tgz",
|
||||||
"integrity": "sha512-cJW4Xd/G3v5ovXtJJ52MAOclqeac9S/aGGgRzLabuF8TnIb6xHvMzKIa6JmrRzUkeXJgfL1MhukP0NK6l39h3A==",
|
"integrity": "sha512-aFi5B0WovBHTEvpM3DzXTUaeN6eN0qWnTkKx4NQaH4Wvcmc153PdaY2UBdSYKaGYw+UyWXSVyxDUg5DoPEttjw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"playwright-core": "1.55.1"
|
"playwright-core": "1.56.1"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"playwright": "cli.js"
|
"playwright": "cli.js"
|
||||||
@@ -1918,9 +1896,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/playwright-core": {
|
"node_modules/playwright-core": {
|
||||||
"version": "1.55.1",
|
"version": "1.56.1",
|
||||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.55.1.tgz",
|
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.56.1.tgz",
|
||||||
"integrity": "sha512-Z6Mh9mkwX+zxSlHqdr5AOcJnfp+xUWLCt9uKV18fhzA8eyxUd8NUWzAjxUh55RZKSYwDGX0cfaySdhZJGMoJ+w==",
|
"integrity": "sha512-hutraynyn31F+Bifme+Ps9Vq59hKuUCz7H1kDOcBs+2oGguKkWTU50bBWrtz34OUWmIwpBTWDxaRPXrIXkgvmQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"playwright-core": "cli.js"
|
"playwright-core": "cli.js"
|
||||||
@@ -1929,35 +1907,6 @@
|
|||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/postcss": {
|
|
||||||
"version": "8.5.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
|
|
||||||
"integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
|
|
||||||
"dev": true,
|
|
||||||
"funding": [
|
|
||||||
{
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/postcss/"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "tidelift",
|
|
||||||
"url": "https://tidelift.com/funding/github/npm/postcss"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "github",
|
|
||||||
"url": "https://github.com/sponsors/ai"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
|
||||||
"nanoid": "^3.3.11",
|
|
||||||
"picocolors": "^1.1.1",
|
|
||||||
"source-map-js": "^1.2.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": "^10 || ^12 || >=14"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/postgres-array": {
|
"node_modules/postgres-array": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
|
||||||
@@ -2049,34 +1998,18 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/raw-body": {
|
"node_modules/raw-body": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz",
|
||||||
"integrity": "sha512-9G8cA+tuMS75+6G/TzW8OtLzmBDMo8p1JRxN5AZ+LAp8uxGA8V8GZm4GQ4/N5QNQEnLmg6SS7wyuSmbKepiKqA==",
|
"integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bytes": "3.1.2",
|
"bytes": "3.1.2",
|
||||||
"http-errors": "2.0.0",
|
"http-errors": "2.0.0",
|
||||||
"iconv-lite": "0.7.0",
|
"iconv-lite": "0.6.3",
|
||||||
"unpipe": "1.0.0"
|
"unpipe": "1.0.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.10"
|
"node": ">= 0.8"
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/raw-body/node_modules/iconv-lite": {
|
|
||||||
"version": "0.7.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz",
|
|
||||||
"integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==",
|
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
|
||||||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.10.0"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/express"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/require-from-string": {
|
"node_modules/require-from-string": {
|
||||||
@@ -2105,9 +2038,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/router/node_modules/debug": {
|
"node_modules/router/node_modules/debug": {
|
||||||
"version": "4.4.3",
|
"version": "4.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
|
||||||
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
|
"integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ms": "^2.1.3"
|
"ms": "^2.1.3"
|
||||||
@@ -2205,9 +2138,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/send/node_modules/debug": {
|
"node_modules/send/node_modules/debug": {
|
||||||
"version": "4.4.3",
|
"version": "4.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
|
||||||
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
|
"integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ms": "^2.1.3"
|
"ms": "^2.1.3"
|
||||||
@@ -2326,29 +2259,20 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/smtp-server": {
|
"node_modules/smtp-server": {
|
||||||
"version": "3.14.0",
|
"version": "3.15.0",
|
||||||
"resolved": "https://registry.npmjs.org/smtp-server/-/smtp-server-3.14.0.tgz",
|
"resolved": "https://registry.npmjs.org/smtp-server/-/smtp-server-3.15.0.tgz",
|
||||||
"integrity": "sha512-cEw/hdIY+xw1pkbQbQ23hvnm9kNABAsgYB+jJYGkzAynZxJ2VB9aqC6JhB1vpdDnqan7C7AL3qHYRGwz5eD6BQ==",
|
"integrity": "sha512-yv945vk0/xcukSKAoIhGz6GOlcXoCyGQH2w9IlLrTKk3SJiOBH9bcO6tD0ILTZYJsMqRa6OTRZAyqeuLXkv59Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"base32.js": "0.1.0",
|
"base32.js": "0.1.0",
|
||||||
"ipv6-normalize": "1.0.1",
|
"ipv6-normalize": "1.0.1",
|
||||||
"nodemailer": "7.0.3",
|
"nodemailer": "7.0.9",
|
||||||
"punycode.js": "2.3.1"
|
"punycode.js": "2.3.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12.0.0"
|
"node": ">=12.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/smtp-server/node_modules/nodemailer": {
|
|
||||||
"version": "7.0.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.3.tgz",
|
|
||||||
"integrity": "sha512-Ajq6Sz1x7cIK3pN6KesGTah+1gnwMnx5gKl3piQlQQE/PwyJ4Mbc8is2psWYxK3RJTVeqsDaCv8ZzXLCDHMTZw==",
|
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
|
||||||
"node": ">=6.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/socket.io": {
|
"node_modules/socket.io": {
|
||||||
"version": "4.8.1",
|
"version": "4.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz",
|
||||||
@@ -2564,30 +2488,30 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/tlds": {
|
"node_modules/tlds": {
|
||||||
"version": "1.259.0",
|
"version": "1.260.0",
|
||||||
"resolved": "https://registry.npmjs.org/tlds/-/tlds-1.259.0.tgz",
|
"resolved": "https://registry.npmjs.org/tlds/-/tlds-1.260.0.tgz",
|
||||||
"integrity": "sha512-AldGGlDP0PNgwppe2quAvuBl18UcjuNtOnDuUkqhd6ipPqrYYBt3aTxK1QTsBVknk97lS2JcafWMghjGWFtunw==",
|
"integrity": "sha512-78+28EWBhCEE7qlyaHA9OR3IPvbCLiDh3Ckla593TksfFc9vfTsgvH7eS+dr3o9qr31gwGbogcI16yN91PoRjQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"tlds": "bin.js"
|
"tlds": "bin.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tldts": {
|
"node_modules/tldts": {
|
||||||
"version": "7.0.16",
|
"version": "7.0.17",
|
||||||
"resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.16.tgz",
|
"resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.17.tgz",
|
||||||
"integrity": "sha512-5bdPHSwbKTeHmXrgecID4Ljff8rQjv7g8zKQPkCozRo2HWWni+p310FSn5ImI+9kWw9kK4lzOB5q/a6iv0IJsw==",
|
"integrity": "sha512-Y1KQBgDd/NUc+LfOtKS6mNsC9CCaH+m2P1RoIZy7RAPo3C3/t8X45+zgut31cRZtZ3xKPjfn3TkGTrctC2TQIQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tldts-core": "^7.0.16"
|
"tldts-core": "^7.0.17"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"tldts": "bin/cli.js"
|
"tldts": "bin/cli.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tldts-core": {
|
"node_modules/tldts-core": {
|
||||||
"version": "7.0.16",
|
"version": "7.0.17",
|
||||||
"resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.16.tgz",
|
"resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.17.tgz",
|
||||||
"integrity": "sha512-XHhPmHxphLi+LGbH0G/O7dmUH9V65OY20R7vH8gETHsp5AZCjBk9l8sqmRKLaGOxnETU7XNSDUPtewAy/K6jbA==",
|
"integrity": "sha512-DieYoGrP78PWKsrXr8MZwtQ7GLCUeLxihtjC1jZsW1DnvSMdKPitJSe8OSYDM2u5H6g3kWJZpePqkp43TfLh0g==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/toidentifier": {
|
"node_modules/toidentifier": {
|
||||||
@@ -2644,9 +2568,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/undici-types": {
|
"node_modules/undici-types": {
|
||||||
"version": "7.12.0",
|
"version": "7.10.0",
|
||||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.12.0.tgz",
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz",
|
||||||
"integrity": "sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ==",
|
"integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/unpipe": {
|
"node_modules/unpipe": {
|
||||||
|
|||||||
@@ -8,13 +8,13 @@
|
|||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@playwright/test": "1.55.1",
|
"@playwright/test": "1.56.1",
|
||||||
"dotenv": "17.2.2",
|
"dotenv": "17.2.3",
|
||||||
"dotenv-expand": "12.0.3",
|
"dotenv-expand": "12.0.3",
|
||||||
"maildev": "npm:@timshel_npm/maildev@^3.2.3"
|
"maildev": "npm:@timshel_npm/maildev@3.2.5"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"mysql2": "3.15.0",
|
"mysql2": "3.15.3",
|
||||||
"otpauth": "9.4.1",
|
"otpauth": "9.4.1",
|
||||||
"pg": "8.16.3"
|
"pg": "8.16.3"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ ROCKET_PORT=8003
|
|||||||
DOMAIN=http://localhost:${ROCKET_PORT}
|
DOMAIN=http://localhost:${ROCKET_PORT}
|
||||||
LOG_LEVEL=info,oidcwarden::sso=debug
|
LOG_LEVEL=info,oidcwarden::sso=debug
|
||||||
LOGIN_RATELIMIT_MAX_BURST=100
|
LOGIN_RATELIMIT_MAX_BURST=100
|
||||||
|
ADMIN_TOKEN=admin
|
||||||
|
|
||||||
SMTP_SECURITY=off
|
SMTP_SECURITY=off
|
||||||
SMTP_PORT=${MAILDEV_SMTP_PORT}
|
SMTP_PORT=${MAILDEV_SMTP_PORT}
|
||||||
@@ -67,8 +68,8 @@ SSO_AUTHORITY=http://${KC_HTTP_HOST}:${KC_HTTP_PORT}/realms/${TEST_REALM}
|
|||||||
SSO_DEBUG_TOKENS=true
|
SSO_DEBUG_TOKENS=true
|
||||||
|
|
||||||
# Custom web-vault build
|
# Custom web-vault build
|
||||||
# PW_WV_REPO_URL=https://github.com/dani-garcia/bw_web_builds.git
|
# PW_VW_REPO_URL=https://github.com/vaultwarden/vw_web_builds.git
|
||||||
# PW_WV_COMMIT_HASH=a5f5390895516bce2f48b7baadb6dc399e5fe75a
|
# PW_VW_COMMIT_HASH=b5f5b2157b9b64b5813bc334a75a277d0377b5d3
|
||||||
|
|
||||||
###########################
|
###########################
|
||||||
# Docker MariaDb container#
|
# Docker MariaDb container#
|
||||||
|
|||||||
@@ -91,6 +91,9 @@ test('2fa', async ({ page }) => {
|
|||||||
await page.getByLabel(/Verification code/).fill(code);
|
await page.getByLabel(/Verification code/).fill(code);
|
||||||
await page.getByRole('button', { name: 'Continue' }).click();
|
await page.getByRole('button', { name: 'Continue' }).click();
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: 'Add it later' }).click();
|
||||||
|
await page.getByRole('link', { name: 'Skip to web app' }).click();
|
||||||
|
|
||||||
await expect(page).toHaveTitle(/Vaults/);
|
await expect(page).toHaveTitle(/Vaults/);
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -57,15 +57,17 @@ test('invited with new account', async ({ page }) => {
|
|||||||
await expect(page).toHaveTitle(/Create account | Vaultwarden Web/);
|
await expect(page).toHaveTitle(/Create account | Vaultwarden Web/);
|
||||||
|
|
||||||
//await page.getByLabel('Name').fill(users.user2.name);
|
//await page.getByLabel('Name').fill(users.user2.name);
|
||||||
await page.getByLabel('New master password (required)', { exact: true }).fill(users.user2.password);
|
await page.getByLabel('Master password (required)', { exact: true }).fill(users.user2.password);
|
||||||
await page.getByLabel('Confirm new master password (').fill(users.user2.password);
|
await page.getByLabel('Confirm master password (').fill(users.user2.password);
|
||||||
await page.getByRole('button', { name: 'Create account' }).click();
|
await page.getByRole('button', { name: 'Create account' }).click();
|
||||||
await utils.checkNotification(page, 'Your new account has been created');
|
await utils.checkNotification(page, 'Your new account has been created');
|
||||||
|
|
||||||
|
await utils.checkNotification(page, 'Invitation accepted');
|
||||||
|
await utils.ignoreExtension(page);
|
||||||
|
|
||||||
// Redirected to the vault
|
// Redirected to the vault
|
||||||
await expect(page).toHaveTitle('Vaults | Vaultwarden Web');
|
await expect(page).toHaveTitle('Vaults | Vaultwarden Web');
|
||||||
await utils.checkNotification(page, 'You have been logged in!');
|
// await utils.checkNotification(page, 'You have been logged in!');
|
||||||
await utils.checkNotification(page, 'Invitation accepted');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await test.step('Check mails', async () => {
|
await test.step('Check mails', async () => {
|
||||||
@@ -91,9 +93,11 @@ test('invited with existing account', async ({ page }) => {
|
|||||||
await page.getByLabel('Master password').fill(users.user3.password);
|
await page.getByLabel('Master password').fill(users.user3.password);
|
||||||
await page.getByRole('button', { name: 'Log in with master password' }).click();
|
await page.getByRole('button', { name: 'Log in with master password' }).click();
|
||||||
|
|
||||||
|
await utils.checkNotification(page, 'Invitation accepted');
|
||||||
|
await utils.ignoreExtension(page);
|
||||||
|
|
||||||
// We are now in the default vault page
|
// We are now in the default vault page
|
||||||
await expect(page).toHaveTitle(/Vaultwarden Web/);
|
await expect(page).toHaveTitle(/Vaultwarden Web/);
|
||||||
await utils.checkNotification(page, 'Invitation accepted');
|
|
||||||
|
|
||||||
await mail3Buffer.expect((m) => m.subject === 'New Device Logged In From Firefox');
|
await mail3Buffer.expect((m) => m.subject === 'New Device Logged In From Firefox');
|
||||||
await mail1Buffer.expect((m) => m.subject.includes('Invitation to Test accepted'));
|
await mail1Buffer.expect((m) => m.subject.includes('Invitation to Test accepted'));
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ export async function activateEmail(test: Test, page: Page, user: { name: string
|
|||||||
await page.getByRole('menuitem', { name: 'Account settings' }).click();
|
await page.getByRole('menuitem', { name: 'Account settings' }).click();
|
||||||
await page.getByRole('link', { name: 'Security' }).click();
|
await page.getByRole('link', { name: 'Security' }).click();
|
||||||
await page.getByRole('link', { name: 'Two-step login' }).click();
|
await page.getByRole('link', { name: 'Two-step login' }).click();
|
||||||
await page.locator('bit-item').filter({ hasText: 'Email Email Enter a code sent' }).getByRole('button').click();
|
await page.locator('bit-item').filter({ hasText: 'Enter a code sent to your email' }).getByRole('button').click();
|
||||||
await page.getByLabel('Master password (required)').fill(user.password);
|
await page.getByLabel('Master password (required)').fill(user.password);
|
||||||
await page.getByRole('button', { name: 'Continue' }).click();
|
await page.getByRole('button', { name: 'Continue' }).click();
|
||||||
await page.getByRole('button', { name: 'Send email' }).click();
|
await page.getByRole('button', { name: 'Send email' }).click();
|
||||||
|
|||||||
@@ -33,19 +33,21 @@ export async function logNewUser(
|
|||||||
|
|
||||||
await test.step('Create Vault account', async () => {
|
await test.step('Create Vault account', async () => {
|
||||||
await expect(page.getByRole('heading', { name: 'Join organisation' })).toBeVisible();
|
await expect(page.getByRole('heading', { name: 'Join organisation' })).toBeVisible();
|
||||||
await page.getByLabel('New master password (required)', { exact: true }).fill(user.password);
|
await page.getByLabel('Master password (required)', { exact: true }).fill(user.password);
|
||||||
await page.getByLabel('Confirm new master password (').fill(user.password);
|
await page.getByLabel('Confirm master password (').fill(user.password);
|
||||||
await page.getByRole('button', { name: 'Create account' }).click();
|
await page.getByRole('button', { name: 'Create account' }).click();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await utils.checkNotification(page, 'Account successfully created!');
|
||||||
|
await utils.checkNotification(page, 'Invitation accepted');
|
||||||
|
|
||||||
|
await utils.ignoreExtension(page);
|
||||||
|
|
||||||
await test.step('Default vault page', async () => {
|
await test.step('Default vault page', async () => {
|
||||||
await expect(page).toHaveTitle(/Vaultwarden Web/);
|
await expect(page).toHaveTitle(/Vaultwarden Web/);
|
||||||
await expect(page.getByTitle('All vaults', { exact: true })).toBeVisible();
|
await expect(page.getByTitle('All vaults', { exact: true })).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
await utils.checkNotification(page, 'Account successfully created!');
|
|
||||||
await utils.checkNotification(page, 'Invitation accepted');
|
|
||||||
|
|
||||||
if( options.mailBuffer ){
|
if( options.mailBuffer ){
|
||||||
let mailBuffer = options.mailBuffer;
|
let mailBuffer = options.mailBuffer;
|
||||||
await test.step('Check emails', async () => {
|
await test.step('Check emails', async () => {
|
||||||
@@ -115,6 +117,8 @@ export async function logUser(
|
|||||||
await page.getByRole('button', { name: 'Unlock' }).click();
|
await page.getByRole('button', { name: 'Unlock' }).click();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await utils.ignoreExtension(page);
|
||||||
|
|
||||||
await test.step('Default vault page', async () => {
|
await test.step('Default vault page', async () => {
|
||||||
await expect(page).toHaveTitle(/Vaultwarden Web/);
|
await expect(page).toHaveTitle(/Vaultwarden Web/);
|
||||||
await expect(page.getByTitle('All vaults', { exact: true })).toBeVisible();
|
await expect(page.getByTitle('All vaults', { exact: true })).toBeVisible();
|
||||||
|
|||||||
@@ -17,15 +17,16 @@ export async function createAccount(test, page: Page, user: { email: string, nam
|
|||||||
await page.getByRole('button', { name: 'Continue' }).click();
|
await page.getByRole('button', { name: 'Continue' }).click();
|
||||||
|
|
||||||
// Vault finish Creation
|
// Vault finish Creation
|
||||||
await page.getByLabel('New master password (required)', { exact: true }).fill(user.password);
|
await page.getByLabel('Master password (required)', { exact: true }).fill(user.password);
|
||||||
await page.getByLabel('Confirm new master password (').fill(user.password);
|
await page.getByLabel('Confirm master password (').fill(user.password);
|
||||||
await page.getByRole('button', { name: 'Create account' }).click();
|
await page.getByRole('button', { name: 'Create account' }).click();
|
||||||
|
|
||||||
await utils.checkNotification(page, 'Your new account has been created')
|
await utils.checkNotification(page, 'Your new account has been created')
|
||||||
|
await utils.ignoreExtension(page);
|
||||||
|
|
||||||
// We are now in the default vault page
|
// We are now in the default vault page
|
||||||
await expect(page).toHaveTitle('Vaults | Vaultwarden Web');
|
await expect(page).toHaveTitle('Vaults | Vaultwarden Web');
|
||||||
await utils.checkNotification(page, 'You have been logged in!');
|
// await utils.checkNotification(page, 'You have been logged in!');
|
||||||
|
|
||||||
if( mailBuffer ){
|
if( mailBuffer ){
|
||||||
await mailBuffer.expect((m) => m.subject === "Welcome");
|
await mailBuffer.expect((m) => m.subject === "Welcome");
|
||||||
@@ -45,6 +46,8 @@ export async function logUser(test, page: Page, user: { email: string, password:
|
|||||||
await page.getByLabel('Master password').fill(user.password);
|
await page.getByLabel('Master password').fill(user.password);
|
||||||
await page.getByRole('button', { name: 'Log in with master password' }).click();
|
await page.getByRole('button', { name: 'Log in with master password' }).click();
|
||||||
|
|
||||||
|
await utils.ignoreExtension(page);
|
||||||
|
|
||||||
// We are now in the default vault page
|
// We are now in the default vault page
|
||||||
await expect(page).toHaveTitle(/Vaultwarden Web/);
|
await expect(page).toHaveTitle(/Vaultwarden Web/);
|
||||||
|
|
||||||
|
|||||||
@@ -67,16 +67,17 @@ test('invited with new account', async ({ page }) => {
|
|||||||
|
|
||||||
await test.step('Create Vault account', async () => {
|
await test.step('Create Vault account', async () => {
|
||||||
await expect(page.getByRole('heading', { name: 'Join organisation' })).toBeVisible();
|
await expect(page.getByRole('heading', { name: 'Join organisation' })).toBeVisible();
|
||||||
await page.getByLabel('New master password (required)', { exact: true }).fill(users.user2.password);
|
await page.getByLabel('Master password (required)', { exact: true }).fill(users.user2.password);
|
||||||
await page.getByLabel('Confirm new master password (').fill(users.user2.password);
|
await page.getByLabel('Confirm master password (').fill(users.user2.password);
|
||||||
await page.getByRole('button', { name: 'Create account' }).click();
|
await page.getByRole('button', { name: 'Create account' }).click();
|
||||||
|
|
||||||
|
await utils.checkNotification(page, 'Account successfully created!');
|
||||||
|
await utils.checkNotification(page, 'Invitation accepted');
|
||||||
|
await utils.ignoreExtension(page);
|
||||||
});
|
});
|
||||||
|
|
||||||
await test.step('Default vault page', async () => {
|
await test.step('Default vault page', async () => {
|
||||||
await expect(page).toHaveTitle(/Vaultwarden Web/);
|
await expect(page).toHaveTitle(/Vaultwarden Web/);
|
||||||
|
|
||||||
await utils.checkNotification(page, 'Account successfully created!');
|
|
||||||
await utils.checkNotification(page, 'Invitation accepted');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await test.step('Check mails', async () => {
|
await test.step('Check mails', async () => {
|
||||||
@@ -107,11 +108,13 @@ test('invited with existing account', async ({ page }) => {
|
|||||||
await expect(page).toHaveTitle('Vaultwarden Web');
|
await expect(page).toHaveTitle('Vaultwarden Web');
|
||||||
await page.getByLabel('Master password').fill(users.user3.password);
|
await page.getByLabel('Master password').fill(users.user3.password);
|
||||||
await page.getByRole('button', { name: 'Unlock' }).click();
|
await page.getByRole('button', { name: 'Unlock' }).click();
|
||||||
|
|
||||||
|
await utils.checkNotification(page, 'Invitation accepted');
|
||||||
|
await utils.ignoreExtension(page);
|
||||||
});
|
});
|
||||||
|
|
||||||
await test.step('Default vault page', async () => {
|
await test.step('Default vault page', async () => {
|
||||||
await expect(page).toHaveTitle(/Vaultwarden Web/);
|
await expect(page).toHaveTitle(/Vaultwarden Web/);
|
||||||
await utils.checkNotification(page, 'Invitation accepted');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await test.step('Check mails', async () => {
|
await test.step('Check mails', async () => {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
[toolchain]
|
[toolchain]
|
||||||
channel = "1.91.0"
|
channel = "1.91.1"
|
||||||
components = [ "rustfmt", "clippy" ]
|
components = [ "rustfmt", "clippy" ]
|
||||||
profile = "minimal"
|
profile = "minimal"
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ use crate::{
|
|||||||
backup_sqlite, get_sql_server_version,
|
backup_sqlite, get_sql_server_version,
|
||||||
models::{
|
models::{
|
||||||
Attachment, Cipher, Collection, Device, Event, EventType, Group, Invitation, Membership, MembershipId,
|
Attachment, Cipher, Collection, Device, Event, EventType, Group, Invitation, Membership, MembershipId,
|
||||||
MembershipType, OrgPolicy, OrgPolicyErr, Organization, OrganizationId, SsoUser, TwoFactor, User, UserId,
|
MembershipType, OrgPolicy, Organization, OrganizationId, SsoUser, TwoFactor, User, UserId,
|
||||||
},
|
},
|
||||||
DbConn, DbConnType, ACTIVE_DB_TYPE,
|
DbConn, DbConnType, ACTIVE_DB_TYPE,
|
||||||
},
|
},
|
||||||
@@ -556,23 +556,9 @@ async fn update_membership_type(data: Json<MembershipTypeData>, token: AdminToke
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
member_to_edit.atype = new_type;
|
||||||
// This check is also done at api::organizations::{accept_invite, _confirm_invite, _activate_member, edit_member}, update_membership_type
|
// This check is also done at api::organizations::{accept_invite, _confirm_invite, _activate_member, edit_member}, update_membership_type
|
||||||
// It returns different error messages per function.
|
OrgPolicy::check_user_allowed(&member_to_edit, "modify", &conn).await?;
|
||||||
if new_type < MembershipType::Admin {
|
|
||||||
match OrgPolicy::is_user_allowed(&member_to_edit.user_uuid, &member_to_edit.org_uuid, true, &conn).await {
|
|
||||||
Ok(_) => {}
|
|
||||||
Err(OrgPolicyErr::TwoFactorMissing) => {
|
|
||||||
if CONFIG.email_2fa_auto_fallback() {
|
|
||||||
two_factor::email::find_and_activate_email_2fa(&member_to_edit.user_uuid, &conn).await?;
|
|
||||||
} else {
|
|
||||||
err!("You cannot modify this user to this type because they have not setup 2FA");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(OrgPolicyErr::SingleOrgEnforced) => {
|
|
||||||
err!("You cannot modify this user to this type because it is a member of an organization which forbids it");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log_event(
|
log_event(
|
||||||
EventType::OrganizationUserUpdated as i32,
|
EventType::OrganizationUserUpdated as i32,
|
||||||
@@ -585,7 +571,6 @@ async fn update_membership_type(data: Json<MembershipTypeData>, token: AdminToke
|
|||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
member_to_edit.atype = new_type;
|
|
||||||
member_to_edit.save(&conn).await
|
member_to_edit.save(&conn).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -75,12 +75,16 @@ pub fn routes() -> Vec<rocket::Route> {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize, Eq, PartialEq)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct KDFData {
|
pub struct KDFData {
|
||||||
|
#[serde(alias = "kdfType")]
|
||||||
kdf: i32,
|
kdf: i32,
|
||||||
|
#[serde(alias = "iterations")]
|
||||||
kdf_iterations: i32,
|
kdf_iterations: i32,
|
||||||
|
#[serde(alias = "memory")]
|
||||||
kdf_memory: Option<i32>,
|
kdf_memory: Option<i32>,
|
||||||
|
#[serde(alias = "parallelism")]
|
||||||
kdf_parallelism: Option<i32>,
|
kdf_parallelism: Option<i32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -545,17 +549,6 @@ async fn post_password(data: Json<ChangePassData>, headers: Headers, conn: DbCon
|
|||||||
save_result
|
save_result
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
struct ChangeKdfData {
|
|
||||||
#[serde(flatten)]
|
|
||||||
kdf: KDFData,
|
|
||||||
|
|
||||||
master_password_hash: String,
|
|
||||||
new_master_password_hash: String,
|
|
||||||
key: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_kdf_data(user: &mut User, data: &KDFData) -> EmptyResult {
|
fn set_kdf_data(user: &mut User, data: &KDFData) -> EmptyResult {
|
||||||
if data.kdf == UserKdfType::Pbkdf2 as i32 && data.kdf_iterations < 100_000 {
|
if data.kdf == UserKdfType::Pbkdf2 as i32 && data.kdf_iterations < 100_000 {
|
||||||
err!("PBKDF2 KDF iterations must be at least 100000.")
|
err!("PBKDF2 KDF iterations must be at least 100000.")
|
||||||
@@ -591,18 +584,61 @@ fn set_kdf_data(user: &mut User, data: &KDFData) -> EmptyResult {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
struct AuthenticationData {
|
||||||
|
salt: String,
|
||||||
|
kdf: KDFData,
|
||||||
|
master_password_authentication_hash: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
struct UnlockData {
|
||||||
|
salt: String,
|
||||||
|
kdf: KDFData,
|
||||||
|
master_key_wrapped_user_key: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
struct ChangeKdfData {
|
||||||
|
new_master_password_hash: String,
|
||||||
|
key: String,
|
||||||
|
authentication_data: AuthenticationData,
|
||||||
|
unlock_data: UnlockData,
|
||||||
|
master_password_hash: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[post("/accounts/kdf", data = "<data>")]
|
#[post("/accounts/kdf", data = "<data>")]
|
||||||
async fn post_kdf(data: Json<ChangeKdfData>, headers: Headers, conn: DbConn, nt: Notify<'_>) -> EmptyResult {
|
async fn post_kdf(data: Json<ChangeKdfData>, headers: Headers, conn: DbConn, nt: Notify<'_>) -> EmptyResult {
|
||||||
let data: ChangeKdfData = data.into_inner();
|
let data: ChangeKdfData = data.into_inner();
|
||||||
let mut user = headers.user;
|
|
||||||
|
|
||||||
if !user.check_valid_password(&data.master_password_hash) {
|
if !headers.user.check_valid_password(&data.master_password_hash) {
|
||||||
err!("Invalid password")
|
err!("Invalid password")
|
||||||
}
|
}
|
||||||
|
|
||||||
set_kdf_data(&mut user, &data.kdf)?;
|
if data.authentication_data.kdf != data.unlock_data.kdf {
|
||||||
|
err!("KDF settings must be equal for authentication and unlock")
|
||||||
|
}
|
||||||
|
|
||||||
user.set_password(&data.new_master_password_hash, Some(data.key), true, None);
|
if headers.user.email != data.authentication_data.salt || headers.user.email != data.unlock_data.salt {
|
||||||
|
err!("Invalid master password salt")
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut user = headers.user;
|
||||||
|
|
||||||
|
set_kdf_data(&mut user, &data.unlock_data.kdf)?;
|
||||||
|
|
||||||
|
user.set_password(
|
||||||
|
&data.authentication_data.master_password_authentication_hash,
|
||||||
|
Some(data.unlock_data.master_key_wrapped_user_key),
|
||||||
|
true,
|
||||||
|
None,
|
||||||
|
);
|
||||||
let save_result = user.save(&conn).await;
|
let save_result = user.save(&conn).await;
|
||||||
|
|
||||||
nt.send_logout(&user, Some(headers.device.uuid.clone()), &conn).await;
|
nt.send_logout(&user, Some(headers.device.uuid.clone()), &conn).await;
|
||||||
|
|||||||
@@ -47,24 +47,11 @@ pub fn routes() -> Vec<Route> {
|
|||||||
|
|
||||||
#[get("/emergency-access/trusted")]
|
#[get("/emergency-access/trusted")]
|
||||||
async fn get_contacts(headers: Headers, conn: DbConn) -> Json<Value> {
|
async fn get_contacts(headers: Headers, conn: DbConn) -> Json<Value> {
|
||||||
if !CONFIG.emergency_access_allowed() {
|
let emergency_access_list = if CONFIG.emergency_access_allowed() {
|
||||||
return Json(json!({
|
EmergencyAccess::find_all_by_grantor_uuid(&headers.user.uuid, &conn).await
|
||||||
"data": [{
|
} else {
|
||||||
"id": "",
|
Vec::new()
|
||||||
"status": 2,
|
};
|
||||||
"type": 0,
|
|
||||||
"waitTimeDays": 0,
|
|
||||||
"granteeId": "",
|
|
||||||
"email": "",
|
|
||||||
"name": "NOTE: Emergency Access is disabled!",
|
|
||||||
"object": "emergencyAccessGranteeDetails",
|
|
||||||
|
|
||||||
}],
|
|
||||||
"object": "list",
|
|
||||||
"continuationToken": null
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
let emergency_access_list = EmergencyAccess::find_all_by_grantor_uuid(&headers.user.uuid, &conn).await;
|
|
||||||
let mut emergency_access_list_json = Vec::with_capacity(emergency_access_list.len());
|
let mut emergency_access_list_json = Vec::with_capacity(emergency_access_list.len());
|
||||||
for ea in emergency_access_list {
|
for ea in emergency_access_list {
|
||||||
if let Some(grantee) = ea.to_json_grantee_details(&conn).await {
|
if let Some(grantee) = ea.to_json_grantee_details(&conn).await {
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ use crate::{
|
|||||||
api::{EmptyResult, JsonResult, Notify, UpdateType},
|
api::{EmptyResult, JsonResult, Notify, UpdateType},
|
||||||
auth::Headers,
|
auth::Headers,
|
||||||
db::{
|
db::{
|
||||||
models::{Membership, MembershipStatus, MembershipType, OrgPolicy, OrgPolicyErr, Organization, User},
|
models::{Membership, MembershipStatus, OrgPolicy, Organization, User},
|
||||||
DbConn,
|
DbConn,
|
||||||
},
|
},
|
||||||
error::Error,
|
error::Error,
|
||||||
@@ -269,27 +269,12 @@ async fn accept_org_invite(
|
|||||||
err!("User already accepted the invitation");
|
err!("User already accepted the invitation");
|
||||||
}
|
}
|
||||||
|
|
||||||
// This check is also done at accept_invite, _confirm_invite, _activate_member, edit_member, admin::update_membership_type
|
|
||||||
// It returns different error messages per function.
|
|
||||||
if member.atype < MembershipType::Admin {
|
|
||||||
match OrgPolicy::is_user_allowed(&member.user_uuid, &member.org_uuid, false, conn).await {
|
|
||||||
Ok(_) => {}
|
|
||||||
Err(OrgPolicyErr::TwoFactorMissing) => {
|
|
||||||
if crate::CONFIG.email_2fa_auto_fallback() {
|
|
||||||
two_factor::email::activate_email_2fa(user, conn).await?;
|
|
||||||
} else {
|
|
||||||
err!("You cannot join this organization until you enable two-step login on your user account");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(OrgPolicyErr::SingleOrgEnforced) => {
|
|
||||||
err!("You cannot join this organization because you are a member of an organization which forbids it");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
member.status = MembershipStatus::Accepted as i32;
|
member.status = MembershipStatus::Accepted as i32;
|
||||||
member.reset_password_key = reset_password_key;
|
member.reset_password_key = reset_password_key;
|
||||||
|
|
||||||
|
// This check is also done at accept_invite, _confirm_invite, _activate_member, edit_member, admin::update_membership_type
|
||||||
|
OrgPolicy::check_user_allowed(&member, "join", conn).await?;
|
||||||
|
|
||||||
member.save(conn).await?;
|
member.save(conn).await?;
|
||||||
|
|
||||||
if crate::CONFIG.mail_enabled() {
|
if crate::CONFIG.mail_enabled() {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ use crate::{
|
|||||||
models::{
|
models::{
|
||||||
Cipher, CipherId, Collection, CollectionCipher, CollectionGroup, CollectionId, CollectionUser, EventType,
|
Cipher, CipherId, Collection, CollectionCipher, CollectionGroup, CollectionId, CollectionUser, EventType,
|
||||||
Group, GroupId, GroupUser, Invitation, Membership, MembershipId, MembershipStatus, MembershipType,
|
Group, GroupId, GroupUser, Invitation, Membership, MembershipId, MembershipStatus, MembershipType,
|
||||||
OrgPolicy, OrgPolicyErr, OrgPolicyType, Organization, OrganizationApiKey, OrganizationId, User, UserId,
|
OrgPolicy, OrgPolicyType, Organization, OrganizationApiKey, OrganizationId, User, UserId,
|
||||||
},
|
},
|
||||||
DbConn,
|
DbConn,
|
||||||
},
|
},
|
||||||
@@ -1463,27 +1463,12 @@ async fn _confirm_invite(
|
|||||||
err!("User in invalid state")
|
err!("User in invalid state")
|
||||||
}
|
}
|
||||||
|
|
||||||
// This check is also done at accept_invite, _confirm_invite, _activate_member, edit_member, admin::update_membership_type
|
|
||||||
// It returns different error messages per function.
|
|
||||||
if member_to_confirm.atype < MembershipType::Admin {
|
|
||||||
match OrgPolicy::is_user_allowed(&member_to_confirm.user_uuid, org_id, true, conn).await {
|
|
||||||
Ok(_) => {}
|
|
||||||
Err(OrgPolicyErr::TwoFactorMissing) => {
|
|
||||||
if CONFIG.email_2fa_auto_fallback() {
|
|
||||||
two_factor::email::find_and_activate_email_2fa(&member_to_confirm.user_uuid, conn).await?;
|
|
||||||
} else {
|
|
||||||
err!("You cannot confirm this user because they have not setup 2FA");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(OrgPolicyErr::SingleOrgEnforced) => {
|
|
||||||
err!("You cannot confirm this user because they are a member of an organization which forbids it");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
member_to_confirm.status = MembershipStatus::Confirmed as i32;
|
member_to_confirm.status = MembershipStatus::Confirmed as i32;
|
||||||
member_to_confirm.akey = key.to_string();
|
member_to_confirm.akey = key.to_string();
|
||||||
|
|
||||||
|
// This check is also done at accept_invite, _confirm_invite, _activate_member, edit_member, admin::update_membership_type
|
||||||
|
OrgPolicy::check_user_allowed(&member_to_confirm, "confirm", conn).await?;
|
||||||
|
|
||||||
log_event(
|
log_event(
|
||||||
EventType::OrganizationUserConfirmed as i32,
|
EventType::OrganizationUserConfirmed as i32,
|
||||||
&member_to_confirm.uuid,
|
&member_to_confirm.uuid,
|
||||||
@@ -1631,27 +1616,13 @@ async fn edit_member(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// This check is also done at accept_invite, _confirm_invite, _activate_member, edit_member, admin::update_membership_type
|
|
||||||
// It returns different error messages per function.
|
|
||||||
if new_type < MembershipType::Admin {
|
|
||||||
match OrgPolicy::is_user_allowed(&member_to_edit.user_uuid, &org_id, true, &conn).await {
|
|
||||||
Ok(_) => {}
|
|
||||||
Err(OrgPolicyErr::TwoFactorMissing) => {
|
|
||||||
if CONFIG.email_2fa_auto_fallback() {
|
|
||||||
two_factor::email::find_and_activate_email_2fa(&member_to_edit.user_uuid, &conn).await?;
|
|
||||||
} else {
|
|
||||||
err!("You cannot modify this user to this type because they have not setup 2FA");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(OrgPolicyErr::SingleOrgEnforced) => {
|
|
||||||
err!("You cannot modify this user to this type because they are a member of an organization which forbids it");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
member_to_edit.access_all = 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;
|
||||||
|
|
||||||
|
// This check is also done at accept_invite, _confirm_invite, _activate_member, edit_member, admin::update_membership_type
|
||||||
|
// We need to perform the check after changing the type since `admin` is exempt.
|
||||||
|
OrgPolicy::check_user_allowed(&member_to_edit, "modify", &conn).await?;
|
||||||
|
|
||||||
// Delete all the odd collections
|
// Delete all the odd collections
|
||||||
for c in CollectionUser::find_by_organization_and_user_uuid(&org_id, &member_to_edit.user_uuid, &conn).await {
|
for c in CollectionUser::find_by_organization_and_user_uuid(&org_id, &member_to_edit.user_uuid, &conn).await {
|
||||||
c.delete(&conn).await?;
|
c.delete(&conn).await?;
|
||||||
@@ -2154,14 +2125,14 @@ async fn put_policy(
|
|||||||
|
|
||||||
// When enabling the SingleOrg policy, remove this org's members that are members of other orgs
|
// When enabling the SingleOrg policy, remove this org's members that are members of other orgs
|
||||||
if pol_type_enum == OrgPolicyType::SingleOrg && data.enabled {
|
if pol_type_enum == OrgPolicyType::SingleOrg && data.enabled {
|
||||||
for member in Membership::find_by_org(&org_id, &conn).await.into_iter() {
|
for mut member in Membership::find_by_org(&org_id, &conn).await.into_iter() {
|
||||||
// Policy only applies to non-Owner/non-Admin members who have accepted joining the org
|
// Policy only applies to non-Owner/non-Admin members who have accepted joining the org
|
||||||
// Exclude invited and revoked users when checking for this policy.
|
// Exclude invited and revoked users when checking for this policy.
|
||||||
// Those users will not be allowed to accept or be activated because of the policy checks done there.
|
// Those users will not be allowed to accept or be activated because of the policy checks done there.
|
||||||
// We check if the count is larger then 1, because it includes this organization also.
|
|
||||||
if member.atype < MembershipType::Admin
|
if member.atype < MembershipType::Admin
|
||||||
&& member.status != MembershipStatus::Invited as i32
|
&& member.status != MembershipStatus::Invited as i32
|
||||||
&& Membership::count_accepted_and_confirmed_by_user(&member.user_uuid, &conn).await > 1
|
&& Membership::count_accepted_and_confirmed_by_user(&member.user_uuid, &member.org_uuid, &conn).await
|
||||||
|
> 0
|
||||||
{
|
{
|
||||||
if CONFIG.mail_enabled() {
|
if CONFIG.mail_enabled() {
|
||||||
let org = Organization::find_by_uuid(&member.org_uuid, &conn).await.unwrap();
|
let org = Organization::find_by_uuid(&member.org_uuid, &conn).await.unwrap();
|
||||||
@@ -2181,7 +2152,8 @@ async fn put_policy(
|
|||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
member.delete(&conn).await?;
|
member.revoke();
|
||||||
|
member.save(&conn).await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2628,25 +2600,10 @@ async fn _restore_member(
|
|||||||
err!("Only owners can restore other owners")
|
err!("Only owners can restore other owners")
|
||||||
}
|
}
|
||||||
|
|
||||||
// This check is also done at accept_invite, _confirm_invite, _activate_member, edit_member, admin::update_membership_type
|
|
||||||
// It returns different error messages per function.
|
|
||||||
if member.atype < MembershipType::Admin {
|
|
||||||
match OrgPolicy::is_user_allowed(&member.user_uuid, org_id, false, conn).await {
|
|
||||||
Ok(_) => {}
|
|
||||||
Err(OrgPolicyErr::TwoFactorMissing) => {
|
|
||||||
if CONFIG.email_2fa_auto_fallback() {
|
|
||||||
two_factor::email::find_and_activate_email_2fa(&member.user_uuid, conn).await?;
|
|
||||||
} else {
|
|
||||||
err!("You cannot restore this user because they have not setup 2FA");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(OrgPolicyErr::SingleOrgEnforced) => {
|
|
||||||
err!("You cannot restore this user because they are a member of an organization which forbids it");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
member.restore();
|
member.restore();
|
||||||
|
// This check is also done at accept_invite, _confirm_invite, _activate_member, edit_member, admin::update_membership_type
|
||||||
|
// This check need to be done after restoring to work with the correct status
|
||||||
|
OrgPolicy::check_user_allowed(&member, "restore", conn).await?;
|
||||||
member.save(conn).await?;
|
member.save(conn).await?;
|
||||||
|
|
||||||
log_event(
|
log_event(
|
||||||
|
|||||||
@@ -568,7 +568,7 @@ async fn post_access_file(
|
|||||||
async fn download_url(host: &Host, send_id: &SendId, file_id: &SendFileId) -> Result<String, crate::Error> {
|
async fn download_url(host: &Host, send_id: &SendId, file_id: &SendFileId) -> Result<String, crate::Error> {
|
||||||
let operator = CONFIG.opendal_operator_for_path_type(&PathType::Sends)?;
|
let operator = CONFIG.opendal_operator_for_path_type(&PathType::Sends)?;
|
||||||
|
|
||||||
if operator.info().scheme() == opendal::Scheme::Fs {
|
if operator.info().scheme() == <&'static str>::from(opendal::Scheme::Fs) {
|
||||||
let token_claims = crate::auth::generate_send_claims(send_id, file_id);
|
let token_claims = crate::auth::generate_send_claims(send_id, file_id);
|
||||||
let token = crate::auth::encode_jwt(&token_claims);
|
let token = crate::auth::encode_jwt(&token_claims);
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ use crate::{
|
|||||||
auth::Headers,
|
auth::Headers,
|
||||||
crypto,
|
crypto,
|
||||||
db::{
|
db::{
|
||||||
models::{DeviceId, EventType, TwoFactor, TwoFactorType, User, UserId},
|
models::{EventType, TwoFactor, TwoFactorType, User, UserId},
|
||||||
DbConn,
|
DbConn,
|
||||||
},
|
},
|
||||||
error::{Error, MapResult},
|
error::{Error, MapResult},
|
||||||
@@ -24,16 +24,10 @@ pub fn routes() -> Vec<Route> {
|
|||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
struct SendEmailLoginData {
|
struct SendEmailLoginData {
|
||||||
#[serde(alias = "DeviceIdentifier")]
|
|
||||||
device_identifier: DeviceId,
|
|
||||||
|
|
||||||
#[allow(unused)]
|
|
||||||
#[serde(alias = "Email")]
|
#[serde(alias = "Email")]
|
||||||
email: Option<String>,
|
email: String,
|
||||||
|
|
||||||
#[allow(unused)]
|
|
||||||
#[serde(alias = "MasterPasswordHash")]
|
#[serde(alias = "MasterPasswordHash")]
|
||||||
master_password_hash: Option<String>,
|
master_password_hash: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// User is trying to login and wants to use email 2FA.
|
/// User is trying to login and wants to use email 2FA.
|
||||||
@@ -45,14 +39,19 @@ async fn send_email_login(data: Json<SendEmailLoginData>, conn: DbConn) -> Empty
|
|||||||
use crate::db::models::User;
|
use crate::db::models::User;
|
||||||
|
|
||||||
// Get the user
|
// Get the user
|
||||||
let Some(user) = User::find_by_device_id(&data.device_identifier, &conn).await else {
|
let Some(user) = User::find_by_mail(&data.email, &conn).await else {
|
||||||
err!("Cannot find user. Try again.")
|
err!("Username or password is incorrect. Try again.")
|
||||||
};
|
};
|
||||||
|
|
||||||
if !CONFIG._enable_email_2fa() {
|
if !CONFIG._enable_email_2fa() {
|
||||||
err!("Email 2FA is disabled")
|
err!("Email 2FA is disabled")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check password
|
||||||
|
if !user.check_valid_password(&data.master_password_hash) {
|
||||||
|
err!("Username or password is incorrect. Try again.")
|
||||||
|
}
|
||||||
|
|
||||||
send_token(&user.uuid, &conn).await?;
|
send_token(&user.uuid, &conn).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -82,19 +82,19 @@ static ICON_SIZE_REGEX: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"(?x)(\d+
|
|||||||
// It is used to prevent sending a specific header which breaks icon downloads.
|
// It is used to prevent sending a specific header which breaks icon downloads.
|
||||||
// If this function needs to be renamed, also adjust the code in `util.rs`
|
// If this function needs to be renamed, also adjust the code in `util.rs`
|
||||||
#[get("/<domain>/icon.png")]
|
#[get("/<domain>/icon.png")]
|
||||||
fn icon_external(domain: &str) -> Option<Redirect> {
|
fn icon_external(domain: &str) -> Cached<Option<Redirect>> {
|
||||||
if !is_valid_domain(domain) {
|
if !is_valid_domain(domain) {
|
||||||
warn!("Invalid domain: {domain}");
|
warn!("Invalid domain: {domain}");
|
||||||
return None;
|
return Cached::ttl(None, CONFIG.icon_cache_negttl(), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if should_block_address(domain) {
|
if should_block_address(domain) {
|
||||||
warn!("Blocked address: {domain}");
|
warn!("Blocked address: {domain}");
|
||||||
return None;
|
return Cached::ttl(None, CONFIG.icon_cache_negttl(), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
let url = CONFIG._icon_service_url().replace("{}", domain);
|
let url = CONFIG._icon_service_url().replace("{}", domain);
|
||||||
match CONFIG.icon_redirect_code() {
|
let redir = match CONFIG.icon_redirect_code() {
|
||||||
301 => Some(Redirect::moved(url)), // legacy permanent redirect
|
301 => Some(Redirect::moved(url)), // legacy permanent redirect
|
||||||
302 => Some(Redirect::found(url)), // legacy temporary redirect
|
302 => Some(Redirect::found(url)), // legacy temporary redirect
|
||||||
307 => Some(Redirect::temporary(url)),
|
307 => Some(Redirect::temporary(url)),
|
||||||
@@ -103,7 +103,8 @@ fn icon_external(domain: &str) -> Option<Redirect> {
|
|||||||
error!("Unexpected redirect code {}", CONFIG.icon_redirect_code());
|
error!("Unexpected redirect code {}", CONFIG.icon_redirect_code());
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
Cached::ttl(redir, CONFIG.icon_cache_ttl(), true)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/<domain>/icon.png")]
|
#[get("/<domain>/icon.png")]
|
||||||
@@ -141,7 +142,7 @@ async fn icon_internal(domain: &str) -> Cached<(ContentType, Vec<u8>)> {
|
|||||||
/// This does some manual checks and makes use of Url to do some basic checking.
|
/// This does some manual checks and makes use of Url to do some basic checking.
|
||||||
/// domains can't be larger then 63 characters (not counting multiple subdomains) according to the RFC's, but we limit the total size to 255.
|
/// domains can't be larger then 63 characters (not counting multiple subdomains) according to the RFC's, but we limit the total size to 255.
|
||||||
fn is_valid_domain(domain: &str) -> bool {
|
fn is_valid_domain(domain: &str) -> bool {
|
||||||
const ALLOWED_CHARS: &str = "_-.";
|
const ALLOWED_CHARS: &str = "-.";
|
||||||
|
|
||||||
// If parsing the domain fails using Url, it will not work with reqwest.
|
// If parsing the domain fails using Url, it will not work with reqwest.
|
||||||
if let Err(parse_error) = url::Url::parse(format!("https://{domain}").as_str()) {
|
if let Err(parse_error) = url::Url::parse(format!("https://{domain}").as_str()) {
|
||||||
|
|||||||
@@ -789,6 +789,10 @@ make_config! {
|
|||||||
/// Bitwarden enforces this by default. In Vaultwarden we encouraged to use multiple organizations because groups were not available.
|
/// Bitwarden enforces this by default. In Vaultwarden we encouraged to use multiple organizations because groups were not available.
|
||||||
/// Setting this to true will enforce the Single Org Policy to be enabled before you can enable the Reset Password policy.
|
/// Setting this to true will enforce the Single Org Policy to be enabled before you can enable the Reset Password policy.
|
||||||
enforce_single_org_with_reset_pw_policy: bool, false, def, false;
|
enforce_single_org_with_reset_pw_policy: bool, false, def, false;
|
||||||
|
|
||||||
|
/// Prefer IPv6 (AAAA) resolving |> This settings configures the DNS resolver to resolve IPv6 first, and if not available try IPv4
|
||||||
|
/// This could be useful in IPv6 only environments.
|
||||||
|
dns_prefer_ipv6: bool, true, def, false;
|
||||||
},
|
},
|
||||||
|
|
||||||
/// OpenID Connect SSO settings
|
/// OpenID Connect SSO settings
|
||||||
@@ -1035,6 +1039,7 @@ fn validate_config(cfg: &ConfigItems) -> Result<(), Error> {
|
|||||||
"ssh-agent",
|
"ssh-agent",
|
||||||
// Key Management Team
|
// Key Management Team
|
||||||
"ssh-key-vault-item",
|
"ssh-key-vault-item",
|
||||||
|
"pm-25373-windows-biometrics-v2",
|
||||||
// Tools
|
// Tools
|
||||||
"export-attachments",
|
"export-attachments",
|
||||||
// Mobile Team
|
// Mobile Team
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ impl Attachment {
|
|||||||
pub async fn get_url(&self, host: &str) -> Result<String, crate::Error> {
|
pub async fn get_url(&self, host: &str) -> Result<String, crate::Error> {
|
||||||
let operator = CONFIG.opendal_operator_for_path_type(&PathType::Attachments)?;
|
let operator = CONFIG.opendal_operator_for_path_type(&PathType::Attachments)?;
|
||||||
|
|
||||||
if operator.info().scheme() == opendal::Scheme::Fs {
|
if operator.info().scheme() == <&'static str>::from(opendal::Scheme::Fs) {
|
||||||
let token = encode_jwt(&generate_file_download_claims(self.cipher_uuid.clone(), self.id.clone()));
|
let token = encode_jwt(&generate_file_download_claims(self.cipher_uuid.clone(), self.id.clone()));
|
||||||
Ok(format!("{host}/attachments/{}/{}?token={token}", self.cipher_uuid, self.id))
|
Ok(format!("{host}/attachments/{}/{}?token={token}", self.cipher_uuid, self.id))
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ pub use self::event::{Event, EventType};
|
|||||||
pub use self::favorite::Favorite;
|
pub use self::favorite::Favorite;
|
||||||
pub use self::folder::{Folder, FolderCipher, FolderId};
|
pub use self::folder::{Folder, FolderCipher, FolderId};
|
||||||
pub use self::group::{CollectionGroup, Group, GroupId, GroupUser};
|
pub use self::group::{CollectionGroup, Group, GroupId, GroupUser};
|
||||||
pub use self::org_policy::{OrgPolicy, OrgPolicyErr, OrgPolicyId, OrgPolicyType};
|
pub use self::org_policy::{OrgPolicy, OrgPolicyId, OrgPolicyType};
|
||||||
pub use self::organization::{
|
pub use self::organization::{
|
||||||
Membership, MembershipId, MembershipStatus, MembershipType, OrgApiKeyId, Organization, OrganizationApiKey,
|
Membership, MembershipId, MembershipStatus, MembershipType, OrgApiKeyId, Organization, OrganizationApiKey,
|
||||||
OrganizationId,
|
OrganizationId,
|
||||||
|
|||||||
@@ -2,10 +2,12 @@ use derive_more::{AsRef, From};
|
|||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
|
use crate::api::core::two_factor;
|
||||||
use crate::api::EmptyResult;
|
use crate::api::EmptyResult;
|
||||||
use crate::db::schema::{org_policies, users_organizations};
|
use crate::db::schema::{org_policies, users_organizations};
|
||||||
use crate::db::DbConn;
|
use crate::db::DbConn;
|
||||||
use crate::error::MapResult;
|
use crate::error::MapResult;
|
||||||
|
use crate::CONFIG;
|
||||||
use diesel::prelude::*;
|
use diesel::prelude::*;
|
||||||
|
|
||||||
use super::{Membership, MembershipId, MembershipStatus, MembershipType, OrganizationId, TwoFactor, UserId};
|
use super::{Membership, MembershipId, MembershipStatus, MembershipType, OrganizationId, TwoFactor, UserId};
|
||||||
@@ -58,14 +60,6 @@ pub struct ResetPasswordDataModel {
|
|||||||
pub auto_enroll_enabled: bool,
|
pub auto_enroll_enabled: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type OrgPolicyResult = Result<(), OrgPolicyErr>;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum OrgPolicyErr {
|
|
||||||
TwoFactorMissing,
|
|
||||||
SingleOrgEnforced,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Local methods
|
/// Local methods
|
||||||
impl OrgPolicy {
|
impl OrgPolicy {
|
||||||
pub fn new(org_uuid: OrganizationId, atype: OrgPolicyType, enabled: bool, data: String) -> Self {
|
pub fn new(org_uuid: OrganizationId, atype: OrgPolicyType, enabled: bool, data: String) -> Self {
|
||||||
@@ -280,31 +274,35 @@ impl OrgPolicy {
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn is_user_allowed(
|
pub async fn check_user_allowed(m: &Membership, action: &str, conn: &DbConn) -> EmptyResult {
|
||||||
user_uuid: &UserId,
|
if m.atype < MembershipType::Admin && m.status > (MembershipStatus::Invited as i32) {
|
||||||
org_uuid: &OrganizationId,
|
|
||||||
exclude_current_org: bool,
|
|
||||||
conn: &DbConn,
|
|
||||||
) -> OrgPolicyResult {
|
|
||||||
// Enforce TwoFactor/TwoStep login
|
// Enforce TwoFactor/TwoStep login
|
||||||
if TwoFactor::find_by_user(user_uuid, conn).await.is_empty() {
|
if let Some(p) = Self::find_by_org_and_type(&m.org_uuid, OrgPolicyType::TwoFactorAuthentication, conn).await
|
||||||
match Self::find_by_org_and_type(org_uuid, OrgPolicyType::TwoFactorAuthentication, conn).await {
|
{
|
||||||
Some(p) if p.enabled => {
|
if p.enabled && TwoFactor::find_by_user(&m.user_uuid, conn).await.is_empty() {
|
||||||
return Err(OrgPolicyErr::TwoFactorMissing);
|
if CONFIG.email_2fa_auto_fallback() {
|
||||||
|
two_factor::email::find_and_activate_email_2fa(&m.user_uuid, conn).await?;
|
||||||
|
} else {
|
||||||
|
err!(format!("Cannot {} because 2FA is required (membership {})", action, m.uuid));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enforce Single Organization Policy of other organizations user is a member of
|
// Check if the user is part of another Organization with SingleOrg activated
|
||||||
// This check here needs to exclude this current org-id, else an accepted user can not be confirmed.
|
if Self::is_applicable_to_user(&m.user_uuid, OrgPolicyType::SingleOrg, Some(&m.org_uuid), conn).await {
|
||||||
let exclude_org = if exclude_current_org {
|
err!(format!(
|
||||||
Some(org_uuid)
|
"Cannot {} because another organization policy forbids it (membership {})",
|
||||||
} else {
|
action, m.uuid
|
||||||
None
|
));
|
||||||
};
|
}
|
||||||
if Self::is_applicable_to_user(user_uuid, OrgPolicyType::SingleOrg, exclude_org, conn).await {
|
|
||||||
return Err(OrgPolicyErr::SingleOrgEnforced);
|
if let Some(p) = Self::find_by_org_and_type(&m.org_uuid, OrgPolicyType::SingleOrg, conn).await {
|
||||||
|
if p.enabled
|
||||||
|
&& Membership::count_accepted_and_confirmed_by_user(&m.user_uuid, &m.org_uuid, conn).await > 0
|
||||||
|
{
|
||||||
|
err!(format!("Cannot {} because the organization policy forbids being part of other organization (membership {})", action, m.uuid));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -883,10 +883,15 @@ impl Membership {
|
|||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn count_accepted_and_confirmed_by_user(user_uuid: &UserId, conn: &DbConn) -> i64 {
|
pub async fn count_accepted_and_confirmed_by_user(
|
||||||
|
user_uuid: &UserId,
|
||||||
|
excluded_org: &OrganizationId,
|
||||||
|
conn: &DbConn,
|
||||||
|
) -> i64 {
|
||||||
db_run! { conn: {
|
db_run! { conn: {
|
||||||
users_organizations::table
|
users_organizations::table
|
||||||
.filter(users_organizations::user_uuid.eq(user_uuid))
|
.filter(users_organizations::user_uuid.eq(user_uuid))
|
||||||
|
.filter(users_organizations::org_uuid.ne(excluded_org))
|
||||||
.filter(users_organizations::status.eq(MembershipStatus::Accepted as i32).or(users_organizations::status.eq(MembershipStatus::Confirmed as i32)))
|
.filter(users_organizations::status.eq(MembershipStatus::Accepted as i32).or(users_organizations::status.eq(MembershipStatus::Confirmed as i32)))
|
||||||
.count()
|
.count()
|
||||||
.first::<i64>(conn)
|
.first::<i64>(conn)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use crate::db::schema::{devices, invitations, sso_users, users};
|
use crate::db::schema::{invitations, sso_users, users};
|
||||||
use chrono::{NaiveDateTime, TimeDelta, Utc};
|
use chrono::{NaiveDateTime, TimeDelta, Utc};
|
||||||
use derive_more::{AsRef, Deref, Display, From};
|
use derive_more::{AsRef, Deref, Display, From};
|
||||||
use diesel::prelude::*;
|
use diesel::prelude::*;
|
||||||
@@ -10,7 +10,6 @@ use super::{
|
|||||||
use crate::{
|
use crate::{
|
||||||
api::EmptyResult,
|
api::EmptyResult,
|
||||||
crypto,
|
crypto,
|
||||||
db::models::DeviceId,
|
|
||||||
db::DbConn,
|
db::DbConn,
|
||||||
error::MapResult,
|
error::MapResult,
|
||||||
sso::OIDCIdentifier,
|
sso::OIDCIdentifier,
|
||||||
@@ -387,17 +386,6 @@ impl User {
|
|||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn find_by_device_id(device_uuid: &DeviceId, conn: &DbConn) -> Option<Self> {
|
|
||||||
db_run! { conn: {
|
|
||||||
users::table
|
|
||||||
.inner_join(devices::table.on(devices::user_uuid.eq(users::uuid)))
|
|
||||||
.filter(devices::uuid.eq(device_uuid))
|
|
||||||
.select(users::all_columns)
|
|
||||||
.first::<Self>(conn)
|
|
||||||
.ok()
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get_all(conn: &DbConn) -> Vec<(Self, Option<SsoUser>)> {
|
pub async fn get_all(conn: &DbConn) -> Vec<(Self, Option<SsoUser>)> {
|
||||||
db_run! { conn: {
|
db_run! { conn: {
|
||||||
users::table
|
users::table
|
||||||
|
|||||||
@@ -185,7 +185,10 @@ impl CustomDnsResolver {
|
|||||||
|
|
||||||
fn new() -> Arc<Self> {
|
fn new() -> Arc<Self> {
|
||||||
match TokioResolver::builder(TokioConnectionProvider::default()) {
|
match TokioResolver::builder(TokioConnectionProvider::default()) {
|
||||||
Ok(builder) => {
|
Ok(mut builder) => {
|
||||||
|
if CONFIG.dns_prefer_ipv6() {
|
||||||
|
builder.options_mut().ip_strategy = hickory_resolver::config::LookupIpStrategy::Ipv6thenIpv4;
|
||||||
|
}
|
||||||
let resolver = builder.build();
|
let resolver = builder.build();
|
||||||
Arc::new(Self::Hickory(Arc::new(resolver)))
|
Arc::new(Self::Hickory(Arc::new(resolver)))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -705,7 +705,7 @@ async fn send_with_selected_transport(email: Message) -> EmptyResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn send_email(address: &str, subject: &str, body_html: String, body_text: String) -> EmptyResult {
|
async fn send_email(address: &str, subject: &str, body_html: String, body_text: String) -> EmptyResult {
|
||||||
let smtp_from = &CONFIG.smtp_from();
|
let smtp_from = Address::from_str(&CONFIG.smtp_from())?;
|
||||||
|
|
||||||
let body = if CONFIG.smtp_embed_images() {
|
let body = if CONFIG.smtp_embed_images() {
|
||||||
let logo_gray_body = Body::new(crate::api::static_files("logo-gray.png").unwrap().1.to_vec());
|
let logo_gray_body = Body::new(crate::api::static_files("logo-gray.png").unwrap().1.to_vec());
|
||||||
@@ -727,9 +727,9 @@ async fn send_email(address: &str, subject: &str, body_html: String, body_text:
|
|||||||
};
|
};
|
||||||
|
|
||||||
let email = Message::builder()
|
let email = Message::builder()
|
||||||
.message_id(Some(format!("<{}@{}>", crate::util::get_uuid(), smtp_from.split('@').collect::<Vec<&str>>()[1])))
|
.message_id(Some(format!("<{}@{}>", crate::util::get_uuid(), smtp_from.domain())))
|
||||||
.to(Mailbox::new(None, Address::from_str(address)?))
|
.to(Mailbox::new(None, Address::from_str(address)?))
|
||||||
.from(Mailbox::new(Some(CONFIG.smtp_from_name()), Address::from_str(smtp_from)?))
|
.from(Mailbox::new(Some(CONFIG.smtp_from_name()), smtp_from))
|
||||||
.subject(subject)
|
.subject(subject)
|
||||||
.multipart(body)?;
|
.multipart(body)?;
|
||||||
|
|
||||||
|
|||||||
@@ -246,8 +246,8 @@ fn init_logging() -> Result<log::LevelFilter, Error> {
|
|||||||
.split(',')
|
.split(',')
|
||||||
.collect::<Vec<&str>>()
|
.collect::<Vec<&str>>()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.flat_map(|s| match s.split('=').collect::<Vec<&str>>()[..] {
|
.flat_map(|s| match s.split_once('=') {
|
||||||
[log, lvl_str] => log::LevelFilter::from_str(lvl_str).ok().map(|lvl| (log, lvl)),
|
Some((log, lvl_str)) => log::LevelFilter::from_str(lvl_str).ok().map(|lvl| (log, lvl)),
|
||||||
_ => None,
|
_ => None,
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
|
|||||||
28
src/sso.rs
28
src/sso.rs
@@ -132,6 +132,12 @@ struct BasicTokenClaims {
|
|||||||
exp: i64,
|
exp: i64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct BasicTokenClaimsValidation {
|
||||||
|
exp: u64,
|
||||||
|
iss: String,
|
||||||
|
}
|
||||||
|
|
||||||
impl BasicTokenClaims {
|
impl BasicTokenClaims {
|
||||||
fn nbf(&self) -> i64 {
|
fn nbf(&self) -> i64 {
|
||||||
self.nbf.or(self.iat).unwrap_or_else(|| Utc::now().timestamp())
|
self.nbf.or(self.iat).unwrap_or_else(|| Utc::now().timestamp())
|
||||||
@@ -139,13 +145,23 @@ impl BasicTokenClaims {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn decode_token_claims(token_name: &str, token: &str) -> ApiResult<BasicTokenClaims> {
|
fn decode_token_claims(token_name: &str, token: &str) -> ApiResult<BasicTokenClaims> {
|
||||||
let mut validation = jsonwebtoken::Validation::default();
|
// We need to manually validate this token, since `insecure_decode` does not do this
|
||||||
validation.set_issuer(&[CONFIG.sso_authority()]);
|
match jsonwebtoken::dangerous::insecure_decode::<BasicTokenClaimsValidation>(token) {
|
||||||
validation.insecure_disable_signature_validation();
|
Ok(btcv) => {
|
||||||
validation.validate_aud = false;
|
let now = jsonwebtoken::get_current_timestamp();
|
||||||
|
let validate_claim = btcv.claims;
|
||||||
|
// Validate the exp in the claim with a leeway of 60 seconds, same as jsonwebtoken does
|
||||||
|
if validate_claim.exp < now - 60 {
|
||||||
|
err_silent!(format!("Expired Signature for base token claim from {token_name}"))
|
||||||
|
}
|
||||||
|
if validate_claim.iss.ne(&CONFIG.sso_authority()) {
|
||||||
|
err_silent!(format!("Invalid Issuer for base token claim from {token_name}"))
|
||||||
|
}
|
||||||
|
|
||||||
match jsonwebtoken::decode(token, &jsonwebtoken::DecodingKey::from_secret(&[]), &validation) {
|
// All is validated and ok, lets decode again using the wanted struct
|
||||||
Ok(btc) => Ok(btc.claims),
|
let btc = jsonwebtoken::dangerous::insecure_decode::<BasicTokenClaims>(token).unwrap();
|
||||||
|
Ok(btc.claims)
|
||||||
|
}
|
||||||
Err(err) => err_silent!(format!("Failed to decode basic token claims from {token_name}: {err}")),
|
Err(err) => err_silent!(format!("Failed to decode basic token claims from {token_name}: {err}")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
17
src/static/scripts/admin.css
vendored
17
src/static/scripts/admin.css
vendored
@@ -58,3 +58,20 @@ img {
|
|||||||
.abbr-badge {
|
.abbr-badge {
|
||||||
cursor: help;
|
cursor: help;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.theme-icon,
|
||||||
|
.theme-icon-active {
|
||||||
|
display: inline-flex;
|
||||||
|
flex: 0 0 1.75em;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-icon svg,
|
||||||
|
.theme-icon-active svg {
|
||||||
|
width: 1.25em;
|
||||||
|
height: 1.25em;
|
||||||
|
min-width: 1.25em;
|
||||||
|
min-height: 1.25em;
|
||||||
|
display: block;
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
15
src/static/scripts/admin.js
vendored
15
src/static/scripts/admin.js
vendored
@@ -1,6 +1,6 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
/* eslint-env es2017, browser */
|
/* eslint-env es2017, browser */
|
||||||
/* exported BASE_URL, _post */
|
/* exported BASE_URL, _post _delete */
|
||||||
|
|
||||||
function getBaseUrl() {
|
function getBaseUrl() {
|
||||||
// If the base URL is `https://vaultwarden.example.com/base/path/admin/`,
|
// If the base URL is `https://vaultwarden.example.com/base/path/admin/`,
|
||||||
@@ -106,7 +106,11 @@ const showActiveTheme = (theme, focus = false) => {
|
|||||||
const themeSwitcherText = document.querySelector("#bd-theme-text");
|
const themeSwitcherText = document.querySelector("#bd-theme-text");
|
||||||
const activeThemeIcon = document.querySelector(".theme-icon-active use");
|
const activeThemeIcon = document.querySelector(".theme-icon-active use");
|
||||||
const btnToActive = document.querySelector(`[data-bs-theme-value="${theme}"]`);
|
const btnToActive = document.querySelector(`[data-bs-theme-value="${theme}"]`);
|
||||||
const svgOfActiveBtn = btnToActive.querySelector("span use").textContent;
|
if (!btnToActive) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const btnIconUse = btnToActive ? btnToActive.querySelector("[data-theme-icon-use]") : null;
|
||||||
|
const iconHref = btnIconUse ? btnIconUse.getAttribute("href") || btnIconUse.getAttribute("xlink:href") : null;
|
||||||
|
|
||||||
document.querySelectorAll("[data-bs-theme-value]").forEach(element => {
|
document.querySelectorAll("[data-bs-theme-value]").forEach(element => {
|
||||||
element.classList.remove("active");
|
element.classList.remove("active");
|
||||||
@@ -115,7 +119,12 @@ const showActiveTheme = (theme, focus = false) => {
|
|||||||
|
|
||||||
btnToActive.classList.add("active");
|
btnToActive.classList.add("active");
|
||||||
btnToActive.setAttribute("aria-pressed", "true");
|
btnToActive.setAttribute("aria-pressed", "true");
|
||||||
activeThemeIcon.textContent = svgOfActiveBtn;
|
|
||||||
|
if (iconHref && activeThemeIcon) {
|
||||||
|
activeThemeIcon.setAttribute("href", iconHref);
|
||||||
|
activeThemeIcon.setAttribute("xlink:href", iconHref);
|
||||||
|
}
|
||||||
|
|
||||||
const themeSwitcherLabel = `${themeSwitcherText.textContent} (${btnToActive.dataset.bsThemeValue})`;
|
const themeSwitcherLabel = `${themeSwitcherText.textContent} (${btnToActive.dataset.bsThemeValue})`;
|
||||||
themeSwitcher.setAttribute("aria-label", themeSwitcherLabel);
|
themeSwitcher.setAttribute("aria-label", themeSwitcherLabel);
|
||||||
|
|
||||||
|
|||||||
2
src/static/scripts/admin_users.js
vendored
2
src/static/scripts/admin_users.js
vendored
@@ -1,6 +1,6 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
/* eslint-env es2017, browser, jquery */
|
/* eslint-env es2017, browser, jquery */
|
||||||
/* global _post:readable, BASE_URL:readable, reload:readable, jdenticon:readable */
|
/* global _post:readable, _delete:readable BASE_URL:readable, reload:readable, jdenticon:readable */
|
||||||
|
|
||||||
function deleteUser(event) {
|
function deleteUser(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|||||||
7
src/static/scripts/bootstrap.bundle.js
vendored
7
src/static/scripts/bootstrap.bundle.js
vendored
@@ -1,5 +1,5 @@
|
|||||||
/*!
|
/*!
|
||||||
* Bootstrap v5.3.7 (https://getbootstrap.com/)
|
* Bootstrap v5.3.8 (https://getbootstrap.com/)
|
||||||
* Copyright 2011-2025 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)
|
||||||
*/
|
*/
|
||||||
@@ -647,7 +647,7 @@
|
|||||||
* Constants
|
* Constants
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const VERSION = '5.3.7';
|
const VERSION = '5.3.8';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class definition
|
* Class definition
|
||||||
@@ -3690,9 +3690,6 @@
|
|||||||
this._element.setAttribute('aria-expanded', 'false');
|
this._element.setAttribute('aria-expanded', 'false');
|
||||||
Manipulator.removeDataAttribute(this._menu, 'popper');
|
Manipulator.removeDataAttribute(this._menu, 'popper');
|
||||||
EventHandler.trigger(this._element, EVENT_HIDDEN$5, relatedTarget);
|
EventHandler.trigger(this._element, EVENT_HIDDEN$5, relatedTarget);
|
||||||
|
|
||||||
// Explicitly return focus to the trigger element
|
|
||||||
this._element.focus();
|
|
||||||
}
|
}
|
||||||
_getConfig(config) {
|
_getConfig(config) {
|
||||||
config = super._getConfig(config);
|
config = super._getConfig(config);
|
||||||
|
|||||||
7
src/static/scripts/bootstrap.css
vendored
7
src/static/scripts/bootstrap.css
vendored
@@ -1,6 +1,6 @@
|
|||||||
@charset "UTF-8";
|
@charset "UTF-8";
|
||||||
/*!
|
/*!
|
||||||
* Bootstrap v5.3.7 (https://getbootstrap.com/)
|
* Bootstrap v5.3.8 (https://getbootstrap.com/)
|
||||||
* Copyright 2011-2025 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)
|
||||||
*/
|
*/
|
||||||
@@ -547,6 +547,10 @@ legend + * {
|
|||||||
-webkit-appearance: textfield;
|
-webkit-appearance: textfield;
|
||||||
outline-offset: -2px;
|
outline-offset: -2px;
|
||||||
}
|
}
|
||||||
|
[type=search]::-webkit-search-cancel-button {
|
||||||
|
cursor: pointer;
|
||||||
|
filter: grayscale(1);
|
||||||
|
}
|
||||||
|
|
||||||
/* rtl:raw:
|
/* rtl:raw:
|
||||||
[type="tel"],
|
[type="tel"],
|
||||||
@@ -6208,6 +6212,7 @@ textarea.form-control-lg {
|
|||||||
.spinner-grow,
|
.spinner-grow,
|
||||||
.spinner-border {
|
.spinner-border {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
flex-shrink: 0;
|
||||||
width: var(--bs-spinner-width);
|
width: var(--bs-spinner-width);
|
||||||
height: var(--bs-spinner-height);
|
height: var(--bs-spinner-height);
|
||||||
vertical-align: var(--bs-spinner-vertical-align);
|
vertical-align: var(--bs-spinner-vertical-align);
|
||||||
|
|||||||
19
src/static/scripts/datatables.css
vendored
19
src/static/scripts/datatables.css
vendored
@@ -4,20 +4,21 @@
|
|||||||
*
|
*
|
||||||
* 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.3.2
|
* https://datatables.net/download/#bs5/dt-2.3.5
|
||||||
*
|
*
|
||||||
* Included libraries:
|
* Included libraries:
|
||||||
* DataTables 2.3.2
|
* DataTables 2.3.5
|
||||||
*/
|
*/
|
||||||
|
|
||||||
: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;
|
||||||
--dt-row-selected-link: 9, 10, 11;
|
--dt-row-selected-link: 228, 228, 228;
|
||||||
--dt-row-stripe: 0, 0, 0;
|
--dt-row-stripe: 0, 0, 0;
|
||||||
--dt-row-hover: 0, 0, 0;
|
--dt-row-hover: 0, 0, 0;
|
||||||
--dt-column-ordering: 0, 0, 0;
|
--dt-column-ordering: 0, 0, 0;
|
||||||
--dt-header-align-items: center;
|
--dt-header-align-items: center;
|
||||||
|
--dt-header-vertical-align: middle;
|
||||||
--dt-html-background: white;
|
--dt-html-background: white;
|
||||||
}
|
}
|
||||||
:root.dark {
|
:root.dark {
|
||||||
@@ -112,7 +113,7 @@ table.dataTable thead > tr > td.dt-ordering-asc span.dt-column-order,
|
|||||||
table.dataTable thead > tr > td.dt-ordering-desc span.dt-column-order {
|
table.dataTable thead > tr > td.dt-ordering-desc span.dt-column-order {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 12px;
|
width: 12px;
|
||||||
height: 20px;
|
height: 24px;
|
||||||
}
|
}
|
||||||
table.dataTable thead > tr > th.dt-orderable-asc span.dt-column-order:before, table.dataTable thead > tr > th.dt-orderable-asc span.dt-column-order:after, table.dataTable thead > tr > th.dt-orderable-desc span.dt-column-order:before, table.dataTable thead > tr > th.dt-orderable-desc span.dt-column-order:after, table.dataTable thead > tr > th.dt-ordering-asc span.dt-column-order:before, table.dataTable thead > tr > th.dt-ordering-asc span.dt-column-order:after, table.dataTable thead > tr > th.dt-ordering-desc span.dt-column-order:before, table.dataTable thead > tr > th.dt-ordering-desc span.dt-column-order:after,
|
table.dataTable thead > tr > th.dt-orderable-asc span.dt-column-order:before, table.dataTable thead > tr > th.dt-orderable-asc span.dt-column-order:after, table.dataTable thead > tr > th.dt-orderable-desc span.dt-column-order:before, table.dataTable thead > tr > th.dt-orderable-desc span.dt-column-order:after, table.dataTable thead > tr > th.dt-ordering-asc span.dt-column-order:before, table.dataTable thead > tr > th.dt-ordering-asc span.dt-column-order:after, table.dataTable thead > tr > th.dt-ordering-desc span.dt-column-order:before, table.dataTable thead > tr > th.dt-ordering-desc span.dt-column-order:after,
|
||||||
table.dataTable thead > tr > td.dt-orderable-asc span.dt-column-order:before,
|
table.dataTable thead > tr > td.dt-orderable-asc span.dt-column-order:before,
|
||||||
@@ -144,7 +145,8 @@ table.dataTable thead > tr > td.dt-ordering-asc span.dt-column-order:before,
|
|||||||
table.dataTable thead > tr > td.dt-ordering-desc span.dt-column-order:after {
|
table.dataTable thead > tr > td.dt-ordering-desc span.dt-column-order:after {
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
}
|
}
|
||||||
table.dataTable thead > tr > th.sorting_desc_disabled span.dt-column-order:after, table.dataTable thead > tr > th.sorting_asc_disabled span.dt-column-order:before,
|
table.dataTable thead > tr > th.dt-orderable-none:not(.dt-ordering-asc, .dt-ordering-desc) span.dt-column-order:empty, table.dataTable thead > tr > th.sorting_desc_disabled span.dt-column-order:after, table.dataTable thead > tr > th.sorting_asc_disabled span.dt-column-order:before,
|
||||||
|
table.dataTable thead > tr > td.dt-orderable-none:not(.dt-ordering-asc, .dt-ordering-desc) span.dt-column-order:empty,
|
||||||
table.dataTable thead > tr > td.sorting_desc_disabled span.dt-column-order:after,
|
table.dataTable thead > tr > td.sorting_desc_disabled span.dt-column-order:after,
|
||||||
table.dataTable thead > tr > td.sorting_asc_disabled span.dt-column-order:before {
|
table.dataTable thead > tr > td.sorting_asc_disabled span.dt-column-order:before {
|
||||||
display: none;
|
display: none;
|
||||||
@@ -340,6 +342,7 @@ table.dataTable thead td,
|
|||||||
table.dataTable tfoot th,
|
table.dataTable tfoot th,
|
||||||
table.dataTable tfoot td {
|
table.dataTable tfoot td {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
vertical-align: var(--dt-header-vertical-align);
|
||||||
}
|
}
|
||||||
table.dataTable thead th.dt-head-left,
|
table.dataTable thead th.dt-head-left,
|
||||||
table.dataTable thead td.dt-head-left,
|
table.dataTable thead td.dt-head-left,
|
||||||
@@ -422,10 +425,6 @@ table.dataTable tbody td.dt-body-nowrap {
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
:root {
|
|
||||||
--dt-header-align-items: flex-end;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*! Bootstrap 5 integration for DataTables
|
/*! Bootstrap 5 integration for DataTables
|
||||||
*
|
*
|
||||||
* ©2020 SpryMedia Ltd, all rights reserved.
|
* ©2020 SpryMedia Ltd, all rights reserved.
|
||||||
@@ -453,7 +452,7 @@ table.table.dataTable > tbody > tr.selected > * {
|
|||||||
color: rgb(var(--dt-row-selected-text));
|
color: rgb(var(--dt-row-selected-text));
|
||||||
}
|
}
|
||||||
table.table.dataTable > tbody > tr.selected a {
|
table.table.dataTable > tbody > tr.selected a {
|
||||||
color: rgb(9, 10, 11);
|
color: rgb(228, 228, 228);
|
||||||
color: rgb(var(--dt-row-selected-link));
|
color: rgb(var(--dt-row-selected-link));
|
||||||
}
|
}
|
||||||
table.table.dataTable.table-striped > tbody > tr:nth-of-type(2n+1) > * {
|
table.table.dataTable.table-striped > tbody > tr:nth-of-type(2n+1) > * {
|
||||||
|
|||||||
494
src/static/scripts/datatables.js
vendored
494
src/static/scripts/datatables.js
vendored
File diff suppressed because it is too large
Load Diff
@@ -11,6 +11,21 @@
|
|||||||
<script src="{{urlpath}}/vw_static/admin.js"></script>
|
<script src="{{urlpath}}/vw_static/admin.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="d-none">
|
||||||
|
<symbol id="vw-icon-sun" viewBox="0 0 24 24">
|
||||||
|
<circle cx="12" cy="12" r="5" fill="currentColor"/>
|
||||||
|
<g stroke="currentColor" stroke-linecap="round" stroke-width="1.5">
|
||||||
|
<path d="M12 2v3M12 19v3M4.22 4.22l2.12 2.12M17.66 17.66l2.12 2.12M2 12h3M19 12h3M4.22 19.78l2.12-2.12M17.66 6.34l2.12-2.12"/>
|
||||||
|
</g>
|
||||||
|
</symbol>
|
||||||
|
<symbol id="vw-icon-moon" viewBox="0 0 24 24">
|
||||||
|
<path fill="currentColor" stroke-width=".8" d="M18.4 17.8A9 8.6 0 0 1 13 2a10.5 10 0 1 0 9 14.4 9.4 9 0 0 1-3.6 1.4"/>
|
||||||
|
</symbol>
|
||||||
|
<symbol id="vw-icon-auto" viewBox="0 0 24 24">
|
||||||
|
<circle cx="12" cy="12" r="9" fill="none" stroke="currentColor" stroke-width="1.5"/>
|
||||||
|
<path fill="currentColor" d="M12 3a9 9 0 1 1 0 18Z"/>
|
||||||
|
</symbol>
|
||||||
|
</svg>
|
||||||
<nav class="navbar navbar-expand-md navbar-dark bg-dark mb-4 shadow fixed-top">
|
<nav class="navbar navbar-expand-md navbar-dark bg-dark mb-4 shadow fixed-top">
|
||||||
<div class="container-xl">
|
<div class="container-xl">
|
||||||
<a class="navbar-brand" href="{{urlpath}}/admin"><img class="vaultwarden-icon" src="{{urlpath}}/vw_static/vaultwarden-icon.png" alt="V">aultwarden Admin</a>
|
<a class="navbar-brand" href="{{urlpath}}/admin"><img class="vaultwarden-icon" src="{{urlpath}}/vw_static/vaultwarden-icon.png" alt="V">aultwarden Admin</a>
|
||||||
@@ -39,14 +54,16 @@
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<ul class="navbar-nav">
|
<ul class="navbar-nav mx-3">
|
||||||
<li class="nav-item dropdown">
|
<li class="nav-item dropdown">
|
||||||
<button
|
<button
|
||||||
class="btn btn-link nav-link py-0 px-0 px-md-2 dropdown-toggle d-flex align-items-center"
|
class="btn btn-link nav-link py-0 px-0 px-md-2 dropdown-toggle d-flex align-items-center"
|
||||||
id="bd-theme" type="button" aria-expanded="false" data-bs-toggle="dropdown"
|
id="bd-theme" type="button" aria-expanded="false" data-bs-toggle="dropdown"
|
||||||
data-bs-display="static" aria-label="Toggle theme (auto)">
|
data-bs-display="static" aria-label="Toggle theme (auto)">
|
||||||
<span class="my-1 fs-4 theme-icon-active">
|
<span class="my-1 fs-4 theme-icon-active">
|
||||||
<use>☯</use>
|
<svg class="vw-theme-icon" focusable="false" aria-hidden="true">
|
||||||
|
<use data-theme-icon-use href="#vw-icon-auto"></use>
|
||||||
|
</svg>
|
||||||
</span>
|
</span>
|
||||||
<span class="d-md-none ms-2" id="bd-theme-text">Toggle theme</span>
|
<span class="d-md-none ms-2" id="bd-theme-text">Toggle theme</span>
|
||||||
</button>
|
</button>
|
||||||
@@ -55,7 +72,9 @@
|
|||||||
<button type="button" class="dropdown-item d-flex align-items-center"
|
<button type="button" class="dropdown-item d-flex align-items-center"
|
||||||
data-bs-theme-value="light" aria-pressed="false">
|
data-bs-theme-value="light" aria-pressed="false">
|
||||||
<span class="me-2 fs-4 theme-icon">
|
<span class="me-2 fs-4 theme-icon">
|
||||||
<use>☀</use>
|
<svg class="vw-theme-icon" focusable="false" aria-hidden="true">
|
||||||
|
<use data-theme-icon-use href="#vw-icon-sun"></use>
|
||||||
|
</svg>
|
||||||
</span>
|
</span>
|
||||||
Light
|
Light
|
||||||
</button>
|
</button>
|
||||||
@@ -64,7 +83,9 @@
|
|||||||
<button type="button" class="dropdown-item d-flex align-items-center"
|
<button type="button" class="dropdown-item d-flex align-items-center"
|
||||||
data-bs-theme-value="dark" aria-pressed="false">
|
data-bs-theme-value="dark" aria-pressed="false">
|
||||||
<span class="me-2 fs-4 theme-icon">
|
<span class="me-2 fs-4 theme-icon">
|
||||||
<use>★</use>
|
<svg class="vw-theme-icon" focusable="false" aria-hidden="true">
|
||||||
|
<use data-theme-icon-use href="#vw-icon-moon"></use>
|
||||||
|
</svg>
|
||||||
</span>
|
</span>
|
||||||
Dark
|
Dark
|
||||||
</button>
|
</button>
|
||||||
@@ -73,7 +94,9 @@
|
|||||||
<button type="button" class="dropdown-item d-flex align-items-center active"
|
<button type="button" class="dropdown-item d-flex align-items-center active"
|
||||||
data-bs-theme-value="auto" aria-pressed="true">
|
data-bs-theme-value="auto" aria-pressed="true">
|
||||||
<span class="me-2 fs-4 theme-icon">
|
<span class="me-2 fs-4 theme-icon">
|
||||||
<use>☯</use>
|
<svg class="vw-theme-icon" focusable="false" aria-hidden="true">
|
||||||
|
<use data-theme-icon-use href="#vw-icon-auto"></use>
|
||||||
|
</svg>
|
||||||
</span>
|
</span>
|
||||||
Auto
|
Auto
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
You have been removed from {{{org_name}}}
|
Your access to {{{org_name}}} has been revoked
|
||||||
<!---------------->
|
<!---------------->
|
||||||
Your user account has been removed from the *{{org_name}}* organization because you are a part of another organization. The {{org_name}} organization has enabled a policy that prevents users from being a part of multiple organizations. Before you can re-join this organization you need to leave all other organizations or join with a different account.
|
Your access to the *{{org_name}}* organization has been revoked because you are a part of another organization. The {{org_name}} organization has enabled a policy that prevents users from being a part of multiple organizations. Before your access can be restored you need to leave all other organizations or join with a different account.
|
||||||
{{> email/email_footer_text }}
|
{{> email/email_footer_text }}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
You have been removed from {{{org_name}}}
|
Your access to {{{org_name}}} has been revoked
|
||||||
<!---------------->
|
<!---------------->
|
||||||
{{> email/email_header }}
|
{{> 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;">
|
<table width="100%" cellpadding="0" cellspacing="0" style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
|
||||||
<tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
|
<tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
|
||||||
<td class="content-block" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; margin: 0; -webkit-font-smoothing: antialiased; padding: 0 0 10px; -webkit-text-size-adjust: none; text-align: center;" valign="top" align="center">
|
<td class="content-block" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; margin: 0; -webkit-font-smoothing: antialiased; padding: 0 0 10px; -webkit-text-size-adjust: none; text-align: center;" valign="top" align="center">
|
||||||
Your user account has been removed from the <b 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;">{{org_name}}</b> organization because you are a part of another organization. The {{org_name}} organization has enabled a policy that prevents users from being a part of multiple organizations. Before you can re-join this organization you need to leave all other organizations or join with a different account.
|
Your access to the <b 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;">{{org_name}}</b> organization has been revoked because you are a part of another organization. The {{org_name}} organization has enabled a policy that prevents users from being a part of multiple organizations. Before your access can be restored you need to leave all other organizations or join with a different account.
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
Reference in New Issue
Block a user