geforkt von mirrored/vaultwarden
Merge branch 'main' into feature/kdf-options
Dieser Commit ist enthalten in:
Commit
5bcee24f88
55 geänderte Dateien mit 831 neuen und 463 gelöschten Zeilen
5
.github/workflows/release.yml
gevendort
5
.github/workflows/release.yml
gevendort
|
@ -48,7 +48,10 @@ jobs:
|
||||||
ports:
|
ports:
|
||||||
- 5000:5000
|
- 5000:5000
|
||||||
env:
|
env:
|
||||||
DOCKER_BUILDKIT: 1 # Disabled for now, but we should look at this because it will speedup building!
|
# Use BuildKit (https://docs.docker.com/build/buildkit/) for better
|
||||||
|
# build performance and the ability to copy extended file attributes
|
||||||
|
# (e.g., for executable capabilities) across build phases.
|
||||||
|
DOCKER_BUILDKIT: 1
|
||||||
# DOCKER_REPO/secrets.DOCKERHUB_REPO needs to be 'index.docker.io/<user>/<repo>'
|
# DOCKER_REPO/secrets.DOCKERHUB_REPO needs to be 'index.docker.io/<user>/<repo>'
|
||||||
DOCKER_REPO: ${{ secrets.DOCKERHUB_REPO }}
|
DOCKER_REPO: ${{ secrets.DOCKERHUB_REPO }}
|
||||||
SOURCE_COMMIT: ${{ github.sha }}
|
SOURCE_COMMIT: ${{ github.sha }}
|
||||||
|
|
|
@ -3,5 +3,7 @@ ignored:
|
||||||
- DL3008
|
- DL3008
|
||||||
# disable explicit version for apk install
|
# disable explicit version for apk install
|
||||||
- DL3018
|
- DL3018
|
||||||
|
# disable check for consecutive `RUN` instructions
|
||||||
|
- DL3059
|
||||||
trustedRegistries:
|
trustedRegistries:
|
||||||
- docker.io
|
- docker.io
|
||||||
|
|
|
@ -8,7 +8,7 @@ resolver = "2"
|
||||||
|
|
||||||
repository = "https://github.com/dani-garcia/vaultwarden"
|
repository = "https://github.com/dani-garcia/vaultwarden"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
license = "GPL-3.0-only"
|
license = "AGPL-3.0-only"
|
||||||
publish = false
|
publish = false
|
||||||
build = "build.rs"
|
build = "build.rs"
|
||||||
|
|
||||||
|
|
143
LICENSE.txt
143
LICENSE.txt
|
@ -1,5 +1,5 @@
|
||||||
GNU GENERAL PUBLIC LICENSE
|
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||||
Version 3, 29 June 2007
|
Version 3, 19 November 2007
|
||||||
|
|
||||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||||
Everyone is permitted to copy and distribute verbatim copies
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
@ -7,17 +7,15 @@
|
||||||
|
|
||||||
Preamble
|
Preamble
|
||||||
|
|
||||||
The GNU General Public License is a free, copyleft license for
|
The GNU Affero General Public License is a free, copyleft license for
|
||||||
software and other kinds of works.
|
software and other kinds of works, specifically designed to ensure
|
||||||
|
cooperation with the community in the case of network server software.
|
||||||
|
|
||||||
The licenses for most software and other practical works are designed
|
The licenses for most software and other practical works are designed
|
||||||
to take away your freedom to share and change the works. By contrast,
|
to take away your freedom to share and change the works. By contrast,
|
||||||
the GNU General Public License is intended to guarantee your freedom to
|
our General Public Licenses are intended to guarantee your freedom to
|
||||||
share and change all versions of a program--to make sure it remains free
|
share and change all versions of a program--to make sure it remains free
|
||||||
software for all its users. We, the Free Software Foundation, use the
|
software for all its users.
|
||||||
GNU General Public License for most of our software; it applies also to
|
|
||||||
any other work released this way by its authors. You can apply it to
|
|
||||||
your programs, too.
|
|
||||||
|
|
||||||
When we speak of free software, we are referring to freedom, not
|
When we speak of free software, we are referring to freedom, not
|
||||||
price. Our General Public Licenses are designed to make sure that you
|
price. Our General Public Licenses are designed to make sure that you
|
||||||
|
@ -26,44 +24,34 @@ them if you wish), that you receive source code or can get it if you
|
||||||
want it, that you can change the software or use pieces of it in new
|
want it, that you can change the software or use pieces of it in new
|
||||||
free programs, and that you know you can do these things.
|
free programs, and that you know you can do these things.
|
||||||
|
|
||||||
To protect your rights, we need to prevent others from denying you
|
Developers that use our General Public Licenses protect your rights
|
||||||
these rights or asking you to surrender the rights. Therefore, you have
|
with two steps: (1) assert copyright on the software, and (2) offer
|
||||||
certain responsibilities if you distribute copies of the software, or if
|
you this License which gives you legal permission to copy, distribute
|
||||||
you modify it: responsibilities to respect the freedom of others.
|
and/or modify the software.
|
||||||
|
|
||||||
For example, if you distribute copies of such a program, whether
|
A secondary benefit of defending all users' freedom is that
|
||||||
gratis or for a fee, you must pass on to the recipients the same
|
improvements made in alternate versions of the program, if they
|
||||||
freedoms that you received. You must make sure that they, too, receive
|
receive widespread use, become available for other developers to
|
||||||
or can get the source code. And you must show them these terms so they
|
incorporate. Many developers of free software are heartened and
|
||||||
know their rights.
|
encouraged by the resulting cooperation. However, in the case of
|
||||||
|
software used on network servers, this result may fail to come about.
|
||||||
|
The GNU General Public License permits making a modified version and
|
||||||
|
letting the public access it on a server without ever releasing its
|
||||||
|
source code to the public.
|
||||||
|
|
||||||
Developers that use the GNU GPL protect your rights with two steps:
|
The GNU Affero General Public License is designed specifically to
|
||||||
(1) assert copyright on the software, and (2) offer you this License
|
ensure that, in such cases, the modified source code becomes available
|
||||||
giving you legal permission to copy, distribute and/or modify it.
|
to the community. It requires the operator of a network server to
|
||||||
|
provide the source code of the modified version running there to the
|
||||||
|
users of that server. Therefore, public use of a modified version, on
|
||||||
|
a publicly accessible server, gives the public access to the source
|
||||||
|
code of the modified version.
|
||||||
|
|
||||||
For the developers' and authors' protection, the GPL clearly explains
|
An older license, called the Affero General Public License and
|
||||||
that there is no warranty for this free software. For both users' and
|
published by Affero, was designed to accomplish similar goals. This is
|
||||||
authors' sake, the GPL requires that modified versions be marked as
|
a different license, not a version of the Affero GPL, but Affero has
|
||||||
changed, so that their problems will not be attributed erroneously to
|
released a new version of the Affero GPL which permits relicensing under
|
||||||
authors of previous versions.
|
this license.
|
||||||
|
|
||||||
Some devices are designed to deny users access to install or run
|
|
||||||
modified versions of the software inside them, although the manufacturer
|
|
||||||
can do so. This is fundamentally incompatible with the aim of
|
|
||||||
protecting users' freedom to change the software. The systematic
|
|
||||||
pattern of such abuse occurs in the area of products for individuals to
|
|
||||||
use, which is precisely where it is most unacceptable. Therefore, we
|
|
||||||
have designed this version of the GPL to prohibit the practice for those
|
|
||||||
products. If such problems arise substantially in other domains, we
|
|
||||||
stand ready to extend this provision to those domains in future versions
|
|
||||||
of the GPL, as needed to protect the freedom of users.
|
|
||||||
|
|
||||||
Finally, every program is threatened constantly by software patents.
|
|
||||||
States should not allow patents to restrict development and use of
|
|
||||||
software on general-purpose computers, but in those that do, we wish to
|
|
||||||
avoid the special danger that patents applied to a free program could
|
|
||||||
make it effectively proprietary. To prevent this, the GPL assures that
|
|
||||||
patents cannot be used to render the program non-free.
|
|
||||||
|
|
||||||
The precise terms and conditions for copying, distribution and
|
The precise terms and conditions for copying, distribution and
|
||||||
modification follow.
|
modification follow.
|
||||||
|
@ -72,7 +60,7 @@ modification follow.
|
||||||
|
|
||||||
0. Definitions.
|
0. Definitions.
|
||||||
|
|
||||||
"This License" refers to version 3 of the GNU General Public License.
|
"This License" refers to version 3 of the GNU Affero General Public License.
|
||||||
|
|
||||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||||
works, such as semiconductor masks.
|
works, such as semiconductor masks.
|
||||||
|
@ -549,35 +537,45 @@ to collect a royalty for further conveying from those to whom you convey
|
||||||
the Program, the only way you could satisfy both those terms and this
|
the Program, the only way you could satisfy both those terms and this
|
||||||
License would be to refrain entirely from conveying the Program.
|
License would be to refrain entirely from conveying the Program.
|
||||||
|
|
||||||
13. Use with the GNU Affero General Public License.
|
13. Remote Network Interaction; Use with the GNU General Public License.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, if you modify the
|
||||||
|
Program, your modified version must prominently offer all users
|
||||||
|
interacting with it remotely through a computer network (if your version
|
||||||
|
supports such interaction) an opportunity to receive the Corresponding
|
||||||
|
Source of your version by providing access to the Corresponding Source
|
||||||
|
from a network server at no charge, through some standard or customary
|
||||||
|
means of facilitating copying of software. This Corresponding Source
|
||||||
|
shall include the Corresponding Source for any work covered by version 3
|
||||||
|
of the GNU General Public License that is incorporated pursuant to the
|
||||||
|
following paragraph.
|
||||||
|
|
||||||
Notwithstanding any other provision of this License, you have
|
Notwithstanding any other provision of this License, you have
|
||||||
permission to link or combine any covered work with a work licensed
|
permission to link or combine any covered work with a work licensed
|
||||||
under version 3 of the GNU Affero General Public License into a single
|
under version 3 of the GNU General Public License into a single
|
||||||
combined work, and to convey the resulting work. The terms of this
|
combined work, and to convey the resulting work. The terms of this
|
||||||
License will continue to apply to the part which is the covered work,
|
License will continue to apply to the part which is the covered work,
|
||||||
but the special requirements of the GNU Affero General Public License,
|
but the work with which it is combined will remain governed by version
|
||||||
section 13, concerning interaction through a network will apply to the
|
3 of the GNU General Public License.
|
||||||
combination as such.
|
|
||||||
|
|
||||||
14. Revised Versions of this License.
|
14. Revised Versions of this License.
|
||||||
|
|
||||||
The Free Software Foundation may publish revised and/or new versions of
|
The Free Software Foundation may publish revised and/or new versions of
|
||||||
the GNU General Public License from time to time. Such new versions will
|
the GNU Affero General Public License from time to time. Such new versions
|
||||||
be similar in spirit to the present version, but may differ in detail to
|
will be similar in spirit to the present version, but may differ in detail to
|
||||||
address new problems or concerns.
|
address new problems or concerns.
|
||||||
|
|
||||||
Each version is given a distinguishing version number. If the
|
Each version is given a distinguishing version number. If the
|
||||||
Program specifies that a certain numbered version of the GNU General
|
Program specifies that a certain numbered version of the GNU Affero General
|
||||||
Public License "or any later version" applies to it, you have the
|
Public License "or any later version" applies to it, you have the
|
||||||
option of following the terms and conditions either of that numbered
|
option of following the terms and conditions either of that numbered
|
||||||
version or of any later version published by the Free Software
|
version or of any later version published by the Free Software
|
||||||
Foundation. If the Program does not specify a version number of the
|
Foundation. If the Program does not specify a version number of the
|
||||||
GNU General Public License, you may choose any version ever published
|
GNU Affero General Public License, you may choose any version ever published
|
||||||
by the Free Software Foundation.
|
by the Free Software Foundation.
|
||||||
|
|
||||||
If the Program specifies that a proxy can decide which future
|
If the Program specifies that a proxy can decide which future
|
||||||
versions of the GNU General Public License can be used, that proxy's
|
versions of the GNU Affero General Public License can be used, that proxy's
|
||||||
public statement of acceptance of a version permanently authorizes you
|
public statement of acceptance of a version permanently authorizes you
|
||||||
to choose that version for the Program.
|
to choose that version for the Program.
|
||||||
|
|
||||||
|
@ -635,40 +633,29 @@ the "copyright" line and a pointer to where the full notice is found.
|
||||||
Copyright (C) <year> <name of author>
|
Copyright (C) <year> <name of author>
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU Affero General Public License as published
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
by the Free Software Foundation, either version 3 of the License, or
|
||||||
(at your option) any later version.
|
(at your option) any later version.
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
This program is distributed in the hope that it will be useful,
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
GNU General Public License for more details.
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
You should have received a copy of the GNU Affero General Public License
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
Also add information on how to contact you by electronic and paper mail.
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
If the program does terminal interaction, make it output a short
|
If your software can interact with users remotely through a computer
|
||||||
notice like this when it starts in an interactive mode:
|
network, you should also make sure that it provides a way for users to
|
||||||
|
get its source. For example, if your program is a web application, its
|
||||||
<program> Copyright (C) <year> <name of author>
|
interface could display a "Source" link that leads users to an archive
|
||||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
of the code. There are many ways you could offer source, and different
|
||||||
This is free software, and you are welcome to redistribute it
|
solutions will be better for different programs; see section 13 for the
|
||||||
under certain conditions; type `show c' for details.
|
specific requirements.
|
||||||
|
|
||||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
|
||||||
parts of the General Public License. Of course, your program's commands
|
|
||||||
might be different; for a GUI interface, you would use an "about box".
|
|
||||||
|
|
||||||
You should also get your employer (if you work as a programmer) or school,
|
You should also get your employer (if you work as a programmer) or school,
|
||||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||||
For more information on this, and how to apply and follow the GNU GPL, see
|
For more information on this, and how to apply and follow the GNU AGPL, see
|
||||||
<https://www.gnu.org/licenses/>.
|
<https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
The GNU General Public License does not permit incorporating your program
|
|
||||||
into proprietary programs. If your program is a subroutine library, you
|
|
||||||
may consider it more useful to permit linking proprietary applications with
|
|
||||||
the library. If this is what you want to do, use the GNU Lesser General
|
|
||||||
Public License instead of this License. But first, please read
|
|
||||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
[![Docker Pulls](https://img.shields.io/docker/pulls/vaultwarden/server.svg)](https://hub.docker.com/r/vaultwarden/server)
|
[![Docker Pulls](https://img.shields.io/docker/pulls/vaultwarden/server.svg)](https://hub.docker.com/r/vaultwarden/server)
|
||||||
[![Dependency Status](https://deps.rs/repo/github/dani-garcia/vaultwarden/status.svg)](https://deps.rs/repo/github/dani-garcia/vaultwarden)
|
[![Dependency Status](https://deps.rs/repo/github/dani-garcia/vaultwarden/status.svg)](https://deps.rs/repo/github/dani-garcia/vaultwarden)
|
||||||
[![GitHub Release](https://img.shields.io/github/release/dani-garcia/vaultwarden.svg)](https://github.com/dani-garcia/vaultwarden/releases/latest)
|
[![GitHub Release](https://img.shields.io/github/release/dani-garcia/vaultwarden.svg)](https://github.com/dani-garcia/vaultwarden/releases/latest)
|
||||||
[![GPL-3.0 Licensed](https://img.shields.io/github/license/dani-garcia/vaultwarden.svg)](https://github.com/dani-garcia/vaultwarden/blob/main/LICENSE.txt)
|
[![AGPL-3.0 Licensed](https://img.shields.io/github/license/dani-garcia/vaultwarden.svg)](https://github.com/dani-garcia/vaultwarden/blob/main/LICENSE.txt)
|
||||||
[![Matrix Chat](https://img.shields.io/matrix/vaultwarden:matrix.org.svg?logo=matrix)](https://matrix.to/#/#vaultwarden:matrix.org)
|
[![Matrix Chat](https://img.shields.io/matrix/vaultwarden:matrix.org.svg?logo=matrix)](https://matrix.to/#/#vaultwarden:matrix.org)
|
||||||
|
|
||||||
Image is based on [Rust implementation of Bitwarden API](https://github.com/dani-garcia/vaultwarden).
|
Image is based on [Rust implementation of Bitwarden API](https://github.com/dani-garcia/vaultwarden).
|
||||||
|
|
|
@ -50,7 +50,7 @@
|
||||||
{% else %}
|
{% else %}
|
||||||
{% set package_arch_target_param = "" %}
|
{% set package_arch_target_param = "" %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if "buildx" in target_file %}
|
{% if "buildkit" in target_file %}
|
||||||
{% set mount_rust_cache = "--mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/.cargo/registry " %}
|
{% set mount_rust_cache = "--mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/.cargo/registry " %}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% set mount_rust_cache = "" %}
|
{% set mount_rust_cache = "" %}
|
||||||
|
@ -83,8 +83,6 @@ FROM vaultwarden/web-vault@{{ vault_image_digest }} as vault
|
||||||
########################## BUILD IMAGE ##########################
|
########################## BUILD IMAGE ##########################
|
||||||
FROM {{ build_stage_base_image }} as build
|
FROM {{ build_stage_base_image }} as build
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Build time options to avoid dpkg warnings and help with reproducible builds.
|
# Build time options to avoid dpkg warnings and help with reproducible builds.
|
||||||
ENV DEBIAN_FRONTEND=noninteractive \
|
ENV DEBIAN_FRONTEND=noninteractive \
|
||||||
LANG=C.UTF-8 \
|
LANG=C.UTF-8 \
|
||||||
|
@ -93,7 +91,6 @@ ENV DEBIAN_FRONTEND=noninteractive \
|
||||||
CARGO_HOME="/root/.cargo" \
|
CARGO_HOME="/root/.cargo" \
|
||||||
USER="root"
|
USER="root"
|
||||||
|
|
||||||
|
|
||||||
# Create CARGO_HOME folder and don't download rust docs
|
# Create CARGO_HOME folder and don't download rust docs
|
||||||
RUN {{ mount_rust_cache -}} mkdir -pv "${CARGO_HOME}" \
|
RUN {{ mount_rust_cache -}} mkdir -pv "${CARGO_HOME}" \
|
||||||
&& rustup set profile minimal
|
&& rustup set profile minimal
|
||||||
|
@ -104,21 +101,20 @@ RUN {{ mount_rust_cache -}} mkdir -pv "${CARGO_HOME}" \
|
||||||
ENV RUSTFLAGS='-Clink-arg=/usr/local/musl/{{ package_arch_target }}/lib/libatomic.a'
|
ENV RUSTFLAGS='-Clink-arg=/usr/local/musl/{{ package_arch_target }}/lib/libatomic.a'
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% elif "arm" in target_file %}
|
{% elif "arm" in target_file %}
|
||||||
#
|
# Install build dependencies for the {{ package_arch_name }} architecture
|
||||||
# Install required build libs for {{ package_arch_name }} architecture.
|
|
||||||
# hadolint ignore=DL3059
|
|
||||||
RUN dpkg --add-architecture {{ package_arch_name }} \
|
RUN dpkg --add-architecture {{ package_arch_name }} \
|
||||||
&& apt-get update \
|
&& apt-get update \
|
||||||
&& apt-get install -y \
|
&& apt-get install -y \
|
||||||
--no-install-recommends \
|
--no-install-recommends \
|
||||||
libssl-dev{{ package_arch_prefix }} \
|
gcc-{{ package_cross_compiler }} \
|
||||||
libc6-dev{{ package_arch_prefix }} \
|
libc6-dev{{ package_arch_prefix }} \
|
||||||
libpq5{{ package_arch_prefix }} \
|
libcap2-bin \
|
||||||
libpq-dev{{ package_arch_prefix }} \
|
|
||||||
libmariadb3{{ package_arch_prefix }} \
|
|
||||||
libmariadb-dev{{ package_arch_prefix }} \
|
libmariadb-dev{{ package_arch_prefix }} \
|
||||||
libmariadb-dev-compat{{ package_arch_prefix }} \
|
libmariadb-dev-compat{{ package_arch_prefix }} \
|
||||||
gcc-{{ package_cross_compiler }} \
|
libmariadb3{{ package_arch_prefix }} \
|
||||||
|
libpq-dev{{ package_arch_prefix }} \
|
||||||
|
libpq5{{ package_arch_prefix }} \
|
||||||
|
libssl-dev{{ package_arch_prefix }} \
|
||||||
#
|
#
|
||||||
# Make sure cargo has the right target config
|
# Make sure cargo has the right target config
|
||||||
&& echo '[target.{{ package_arch_target }}]' >> "${CARGO_HOME}/config" \
|
&& echo '[target.{{ package_arch_target }}]' >> "${CARGO_HOME}/config" \
|
||||||
|
@ -130,16 +126,14 @@ ENV CC_{{ package_arch_target | replace("-", "_") }}="/usr/bin/{{ package_cross_
|
||||||
CROSS_COMPILE="1" \
|
CROSS_COMPILE="1" \
|
||||||
OPENSSL_INCLUDE_DIR="/usr/include/{{ package_cross_compiler }}" \
|
OPENSSL_INCLUDE_DIR="/usr/include/{{ package_cross_compiler }}" \
|
||||||
OPENSSL_LIB_DIR="/usr/lib/{{ package_cross_compiler }}"
|
OPENSSL_LIB_DIR="/usr/lib/{{ package_cross_compiler }}"
|
||||||
|
|
||||||
{% elif "amd64" in target_file %}
|
{% elif "amd64" in target_file %}
|
||||||
# Install DB packages
|
# Install build dependencies
|
||||||
RUN apt-get update \
|
RUN apt-get update \
|
||||||
&& apt-get install -y \
|
&& apt-get install -y \
|
||||||
--no-install-recommends \
|
--no-install-recommends \
|
||||||
libmariadb-dev{{ package_arch_prefix }} \
|
libcap2-bin \
|
||||||
libpq-dev{{ package_arch_prefix }} \
|
libmariadb-dev \
|
||||||
&& apt-get clean \
|
libpq-dev
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
# Creates a dummy project used to grab dependencies
|
# Creates a dummy project used to grab dependencies
|
||||||
|
@ -178,9 +172,20 @@ RUN touch src/main.rs
|
||||||
|
|
||||||
# Builds again, this time it'll just be
|
# Builds again, this time it'll just be
|
||||||
# your actual source files being built
|
# your actual source files being built
|
||||||
# hadolint ignore=DL3059
|
|
||||||
RUN {{ mount_rust_cache -}} cargo build --features ${DB} --release{{ package_arch_target_param }}
|
RUN {{ mount_rust_cache -}} cargo build --features ${DB} --release{{ package_arch_target_param }}
|
||||||
|
|
||||||
|
{% if "buildkit" in target_file %}
|
||||||
|
# Add the `cap_net_bind_service` capability to allow listening on
|
||||||
|
# privileged (< 1024) ports even when running as a non-root user.
|
||||||
|
# This is only done if building with BuildKit; with the legacy
|
||||||
|
# builder, the `COPY` instruction doesn't carry over capabilities.
|
||||||
|
{% if package_arch_target is defined %}
|
||||||
|
RUN setcap cap_net_bind_service=+ep target/{{ package_arch_target }}/release/vaultwarden
|
||||||
|
{% else %}
|
||||||
|
RUN setcap cap_net_bind_service=+ep target/release/vaultwarden
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
######################## RUNTIME IMAGE ########################
|
######################## RUNTIME IMAGE ########################
|
||||||
# Create a new stage with a minimal image
|
# Create a new stage with a minimal image
|
||||||
# because we already have a binary built
|
# because we already have a binary built
|
||||||
|
@ -195,7 +200,6 @@ ENV ROCKET_PROFILE="release" \
|
||||||
|
|
||||||
|
|
||||||
{% if "amd64" not in target_file %}
|
{% if "amd64" not in target_file %}
|
||||||
# hadolint ignore=DL3059
|
|
||||||
RUN [ "cross-build-start" ]
|
RUN [ "cross-build-start" ]
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
@ -203,18 +207,18 @@ RUN [ "cross-build-start" ]
|
||||||
RUN mkdir /data \
|
RUN mkdir /data \
|
||||||
{% if "alpine" in runtime_stage_base_image %}
|
{% if "alpine" in runtime_stage_base_image %}
|
||||||
&& apk add --no-cache \
|
&& apk add --no-cache \
|
||||||
openssl \
|
ca-certificates \
|
||||||
tzdata \
|
|
||||||
curl \
|
curl \
|
||||||
ca-certificates
|
openssl \
|
||||||
|
tzdata
|
||||||
{% else %}
|
{% else %}
|
||||||
&& apt-get update && apt-get install -y \
|
&& apt-get update && apt-get install -y \
|
||||||
--no-install-recommends \
|
--no-install-recommends \
|
||||||
openssl \
|
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
curl \
|
curl \
|
||||||
libmariadb-dev-compat \
|
libmariadb-dev-compat \
|
||||||
libpq5 \
|
libpq5 \
|
||||||
|
openssl \
|
||||||
&& apt-get clean \
|
&& apt-get clean \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -222,13 +226,11 @@ RUN mkdir /data \
|
||||||
{% if "armv6" in target_file and "alpine" not in target_file %}
|
{% if "armv6" in target_file and "alpine" not in target_file %}
|
||||||
# In the Balena Bullseye images for armv6/rpi-debian there is a missing symlink.
|
# In the Balena Bullseye images for armv6/rpi-debian there is a missing symlink.
|
||||||
# This symlink was there in the buster images, and for some reason this is needed.
|
# This symlink was there in the buster images, and for some reason this is needed.
|
||||||
# hadolint ignore=DL3059
|
|
||||||
RUN ln -v -s /lib/ld-linux-armhf.so.3 /lib/ld-linux.so.3
|
RUN ln -v -s /lib/ld-linux-armhf.so.3 /lib/ld-linux.so.3
|
||||||
|
|
||||||
{% endif -%}
|
{% endif -%}
|
||||||
|
|
||||||
{% if "amd64" not in target_file %}
|
{% if "amd64" not in target_file %}
|
||||||
# hadolint ignore=DL3059
|
|
||||||
RUN [ "cross-build-end" ]
|
RUN [ "cross-build-end" ]
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
|
|
@ -8,8 +8,8 @@ all: $(OBJECTS)
|
||||||
%/Dockerfile.alpine: Dockerfile.j2 render_template
|
%/Dockerfile.alpine: Dockerfile.j2 render_template
|
||||||
./render_template "$<" "{\"target_file\":\"$@\"}" > "$@"
|
./render_template "$<" "{\"target_file\":\"$@\"}" > "$@"
|
||||||
|
|
||||||
%/Dockerfile.buildx: Dockerfile.j2 render_template
|
%/Dockerfile.buildkit: Dockerfile.j2 render_template
|
||||||
./render_template "$<" "{\"target_file\":\"$@\"}" > "$@"
|
./render_template "$<" "{\"target_file\":\"$@\"}" > "$@"
|
||||||
|
|
||||||
%/Dockerfile.buildx.alpine: Dockerfile.j2 render_template
|
%/Dockerfile.buildkit.alpine: Dockerfile.j2 render_template
|
||||||
./render_template "$<" "{\"target_file\":\"$@\"}" > "$@"
|
./render_template "$<" "{\"target_file\":\"$@\"}" > "$@"
|
||||||
|
|
|
@ -29,8 +29,6 @@ FROM vaultwarden/web-vault@sha256:d5f71fb05c4b87935bf51d84140db0f8716cabfe2974fb
|
||||||
########################## BUILD IMAGE ##########################
|
########################## BUILD IMAGE ##########################
|
||||||
FROM rust:1.66-bullseye as build
|
FROM rust:1.66-bullseye as build
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Build time options to avoid dpkg warnings and help with reproducible builds.
|
# Build time options to avoid dpkg warnings and help with reproducible builds.
|
||||||
ENV DEBIAN_FRONTEND=noninteractive \
|
ENV DEBIAN_FRONTEND=noninteractive \
|
||||||
LANG=C.UTF-8 \
|
LANG=C.UTF-8 \
|
||||||
|
@ -39,19 +37,17 @@ ENV DEBIAN_FRONTEND=noninteractive \
|
||||||
CARGO_HOME="/root/.cargo" \
|
CARGO_HOME="/root/.cargo" \
|
||||||
USER="root"
|
USER="root"
|
||||||
|
|
||||||
|
|
||||||
# Create CARGO_HOME folder and don't download rust docs
|
# Create CARGO_HOME folder and don't download rust docs
|
||||||
RUN mkdir -pv "${CARGO_HOME}" \
|
RUN mkdir -pv "${CARGO_HOME}" \
|
||||||
&& rustup set profile minimal
|
&& rustup set profile minimal
|
||||||
|
|
||||||
# Install DB packages
|
# Install build dependencies
|
||||||
RUN apt-get update \
|
RUN apt-get update \
|
||||||
&& apt-get install -y \
|
&& apt-get install -y \
|
||||||
--no-install-recommends \
|
--no-install-recommends \
|
||||||
|
libcap2-bin \
|
||||||
libmariadb-dev \
|
libmariadb-dev \
|
||||||
libpq-dev \
|
libpq-dev
|
||||||
&& apt-get clean \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
# Creates a dummy project used to grab dependencies
|
# Creates a dummy project used to grab dependencies
|
||||||
RUN USER=root cargo new --bin /app
|
RUN USER=root cargo new --bin /app
|
||||||
|
@ -81,9 +77,9 @@ RUN touch src/main.rs
|
||||||
|
|
||||||
# Builds again, this time it'll just be
|
# Builds again, this time it'll just be
|
||||||
# your actual source files being built
|
# your actual source files being built
|
||||||
# hadolint ignore=DL3059
|
|
||||||
RUN cargo build --features ${DB} --release
|
RUN cargo build --features ${DB} --release
|
||||||
|
|
||||||
|
|
||||||
######################## RUNTIME IMAGE ########################
|
######################## RUNTIME IMAGE ########################
|
||||||
# Create a new stage with a minimal image
|
# Create a new stage with a minimal image
|
||||||
# because we already have a binary built
|
# because we already have a binary built
|
||||||
|
@ -98,11 +94,11 @@ ENV ROCKET_PROFILE="release" \
|
||||||
RUN mkdir /data \
|
RUN mkdir /data \
|
||||||
&& apt-get update && apt-get install -y \
|
&& apt-get update && apt-get install -y \
|
||||||
--no-install-recommends \
|
--no-install-recommends \
|
||||||
openssl \
|
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
curl \
|
curl \
|
||||||
libmariadb-dev-compat \
|
libmariadb-dev-compat \
|
||||||
libpq5 \
|
libpq5 \
|
||||||
|
openssl \
|
||||||
&& apt-get clean \
|
&& apt-get clean \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
|
|
@ -29,8 +29,6 @@ FROM vaultwarden/web-vault@sha256:d5f71fb05c4b87935bf51d84140db0f8716cabfe2974fb
|
||||||
########################## BUILD IMAGE ##########################
|
########################## BUILD IMAGE ##########################
|
||||||
FROM blackdex/rust-musl:x86_64-musl-stable-1.66.1 as build
|
FROM blackdex/rust-musl:x86_64-musl-stable-1.66.1 as build
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Build time options to avoid dpkg warnings and help with reproducible builds.
|
# Build time options to avoid dpkg warnings and help with reproducible builds.
|
||||||
ENV DEBIAN_FRONTEND=noninteractive \
|
ENV DEBIAN_FRONTEND=noninteractive \
|
||||||
LANG=C.UTF-8 \
|
LANG=C.UTF-8 \
|
||||||
|
@ -39,7 +37,6 @@ ENV DEBIAN_FRONTEND=noninteractive \
|
||||||
CARGO_HOME="/root/.cargo" \
|
CARGO_HOME="/root/.cargo" \
|
||||||
USER="root"
|
USER="root"
|
||||||
|
|
||||||
|
|
||||||
# Create CARGO_HOME folder and don't download rust docs
|
# Create CARGO_HOME folder and don't download rust docs
|
||||||
RUN mkdir -pv "${CARGO_HOME}" \
|
RUN mkdir -pv "${CARGO_HOME}" \
|
||||||
&& rustup set profile minimal
|
&& rustup set profile minimal
|
||||||
|
@ -75,9 +72,9 @@ RUN touch src/main.rs
|
||||||
|
|
||||||
# Builds again, this time it'll just be
|
# Builds again, this time it'll just be
|
||||||
# your actual source files being built
|
# your actual source files being built
|
||||||
# hadolint ignore=DL3059
|
|
||||||
RUN cargo build --features ${DB} --release --target=x86_64-unknown-linux-musl
|
RUN cargo build --features ${DB} --release --target=x86_64-unknown-linux-musl
|
||||||
|
|
||||||
|
|
||||||
######################## RUNTIME IMAGE ########################
|
######################## RUNTIME IMAGE ########################
|
||||||
# Create a new stage with a minimal image
|
# Create a new stage with a minimal image
|
||||||
# because we already have a binary built
|
# because we already have a binary built
|
||||||
|
@ -93,10 +90,10 @@ ENV ROCKET_PROFILE="release" \
|
||||||
# Create data folder and Install needed libraries
|
# Create data folder and Install needed libraries
|
||||||
RUN mkdir /data \
|
RUN mkdir /data \
|
||||||
&& apk add --no-cache \
|
&& apk add --no-cache \
|
||||||
openssl \
|
ca-certificates \
|
||||||
tzdata \
|
|
||||||
curl \
|
curl \
|
||||||
ca-certificates
|
openssl \
|
||||||
|
tzdata
|
||||||
|
|
||||||
|
|
||||||
VOLUME /data
|
VOLUME /data
|
||||||
|
|
|
@ -29,8 +29,6 @@ FROM vaultwarden/web-vault@sha256:d5f71fb05c4b87935bf51d84140db0f8716cabfe2974fb
|
||||||
########################## BUILD IMAGE ##########################
|
########################## BUILD IMAGE ##########################
|
||||||
FROM rust:1.66-bullseye as build
|
FROM rust:1.66-bullseye as build
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Build time options to avoid dpkg warnings and help with reproducible builds.
|
# Build time options to avoid dpkg warnings and help with reproducible builds.
|
||||||
ENV DEBIAN_FRONTEND=noninteractive \
|
ENV DEBIAN_FRONTEND=noninteractive \
|
||||||
LANG=C.UTF-8 \
|
LANG=C.UTF-8 \
|
||||||
|
@ -39,19 +37,17 @@ ENV DEBIAN_FRONTEND=noninteractive \
|
||||||
CARGO_HOME="/root/.cargo" \
|
CARGO_HOME="/root/.cargo" \
|
||||||
USER="root"
|
USER="root"
|
||||||
|
|
||||||
|
|
||||||
# Create CARGO_HOME folder and don't download rust docs
|
# Create CARGO_HOME folder and don't download rust docs
|
||||||
RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/.cargo/registry mkdir -pv "${CARGO_HOME}" \
|
RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/.cargo/registry mkdir -pv "${CARGO_HOME}" \
|
||||||
&& rustup set profile minimal
|
&& rustup set profile minimal
|
||||||
|
|
||||||
# Install DB packages
|
# Install build dependencies
|
||||||
RUN apt-get update \
|
RUN apt-get update \
|
||||||
&& apt-get install -y \
|
&& apt-get install -y \
|
||||||
--no-install-recommends \
|
--no-install-recommends \
|
||||||
|
libcap2-bin \
|
||||||
libmariadb-dev \
|
libmariadb-dev \
|
||||||
libpq-dev \
|
libpq-dev
|
||||||
&& apt-get clean \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
# Creates a dummy project used to grab dependencies
|
# Creates a dummy project used to grab dependencies
|
||||||
RUN USER=root cargo new --bin /app
|
RUN USER=root cargo new --bin /app
|
||||||
|
@ -81,9 +77,14 @@ RUN touch src/main.rs
|
||||||
|
|
||||||
# Builds again, this time it'll just be
|
# Builds again, this time it'll just be
|
||||||
# your actual source files being built
|
# your actual source files being built
|
||||||
# hadolint ignore=DL3059
|
|
||||||
RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/.cargo/registry cargo build --features ${DB} --release
|
RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/.cargo/registry cargo build --features ${DB} --release
|
||||||
|
|
||||||
|
# Add the `cap_net_bind_service` capability to allow listening on
|
||||||
|
# privileged (< 1024) ports even when running as a non-root user.
|
||||||
|
# This is only done if building with BuildKit; with the legacy
|
||||||
|
# builder, the `COPY` instruction doesn't carry over capabilities.
|
||||||
|
RUN setcap cap_net_bind_service=+ep target/release/vaultwarden
|
||||||
|
|
||||||
######################## RUNTIME IMAGE ########################
|
######################## RUNTIME IMAGE ########################
|
||||||
# Create a new stage with a minimal image
|
# Create a new stage with a minimal image
|
||||||
# because we already have a binary built
|
# because we already have a binary built
|
||||||
|
@ -98,11 +99,11 @@ ENV ROCKET_PROFILE="release" \
|
||||||
RUN mkdir /data \
|
RUN mkdir /data \
|
||||||
&& apt-get update && apt-get install -y \
|
&& apt-get update && apt-get install -y \
|
||||||
--no-install-recommends \
|
--no-install-recommends \
|
||||||
openssl \
|
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
curl \
|
curl \
|
||||||
libmariadb-dev-compat \
|
libmariadb-dev-compat \
|
||||||
libpq5 \
|
libpq5 \
|
||||||
|
openssl \
|
||||||
&& apt-get clean \
|
&& apt-get clean \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
|
@ -29,8 +29,6 @@ FROM vaultwarden/web-vault@sha256:d5f71fb05c4b87935bf51d84140db0f8716cabfe2974fb
|
||||||
########################## BUILD IMAGE ##########################
|
########################## BUILD IMAGE ##########################
|
||||||
FROM blackdex/rust-musl:x86_64-musl-stable-1.66.1 as build
|
FROM blackdex/rust-musl:x86_64-musl-stable-1.66.1 as build
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Build time options to avoid dpkg warnings and help with reproducible builds.
|
# Build time options to avoid dpkg warnings and help with reproducible builds.
|
||||||
ENV DEBIAN_FRONTEND=noninteractive \
|
ENV DEBIAN_FRONTEND=noninteractive \
|
||||||
LANG=C.UTF-8 \
|
LANG=C.UTF-8 \
|
||||||
|
@ -39,7 +37,6 @@ ENV DEBIAN_FRONTEND=noninteractive \
|
||||||
CARGO_HOME="/root/.cargo" \
|
CARGO_HOME="/root/.cargo" \
|
||||||
USER="root"
|
USER="root"
|
||||||
|
|
||||||
|
|
||||||
# Create CARGO_HOME folder and don't download rust docs
|
# Create CARGO_HOME folder and don't download rust docs
|
||||||
RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/.cargo/registry mkdir -pv "${CARGO_HOME}" \
|
RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/.cargo/registry mkdir -pv "${CARGO_HOME}" \
|
||||||
&& rustup set profile minimal
|
&& rustup set profile minimal
|
||||||
|
@ -75,9 +72,14 @@ RUN touch src/main.rs
|
||||||
|
|
||||||
# Builds again, this time it'll just be
|
# Builds again, this time it'll just be
|
||||||
# your actual source files being built
|
# your actual source files being built
|
||||||
# hadolint ignore=DL3059
|
|
||||||
RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/.cargo/registry cargo build --features ${DB} --release --target=x86_64-unknown-linux-musl
|
RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/.cargo/registry cargo build --features ${DB} --release --target=x86_64-unknown-linux-musl
|
||||||
|
|
||||||
|
# Add the `cap_net_bind_service` capability to allow listening on
|
||||||
|
# privileged (< 1024) ports even when running as a non-root user.
|
||||||
|
# This is only done if building with BuildKit; with the legacy
|
||||||
|
# builder, the `COPY` instruction doesn't carry over capabilities.
|
||||||
|
RUN setcap cap_net_bind_service=+ep target/x86_64-unknown-linux-musl/release/vaultwarden
|
||||||
|
|
||||||
######################## RUNTIME IMAGE ########################
|
######################## RUNTIME IMAGE ########################
|
||||||
# Create a new stage with a minimal image
|
# Create a new stage with a minimal image
|
||||||
# because we already have a binary built
|
# because we already have a binary built
|
||||||
|
@ -93,10 +95,10 @@ ENV ROCKET_PROFILE="release" \
|
||||||
# Create data folder and Install needed libraries
|
# Create data folder and Install needed libraries
|
||||||
RUN mkdir /data \
|
RUN mkdir /data \
|
||||||
&& apk add --no-cache \
|
&& apk add --no-cache \
|
||||||
openssl \
|
ca-certificates \
|
||||||
tzdata \
|
|
||||||
curl \
|
curl \
|
||||||
ca-certificates
|
openssl \
|
||||||
|
tzdata
|
||||||
|
|
||||||
|
|
||||||
VOLUME /data
|
VOLUME /data
|
|
@ -29,8 +29,6 @@ FROM vaultwarden/web-vault@sha256:d5f71fb05c4b87935bf51d84140db0f8716cabfe2974fb
|
||||||
########################## BUILD IMAGE ##########################
|
########################## BUILD IMAGE ##########################
|
||||||
FROM rust:1.66-bullseye as build
|
FROM rust:1.66-bullseye as build
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Build time options to avoid dpkg warnings and help with reproducible builds.
|
# Build time options to avoid dpkg warnings and help with reproducible builds.
|
||||||
ENV DEBIAN_FRONTEND=noninteractive \
|
ENV DEBIAN_FRONTEND=noninteractive \
|
||||||
LANG=C.UTF-8 \
|
LANG=C.UTF-8 \
|
||||||
|
@ -39,26 +37,24 @@ ENV DEBIAN_FRONTEND=noninteractive \
|
||||||
CARGO_HOME="/root/.cargo" \
|
CARGO_HOME="/root/.cargo" \
|
||||||
USER="root"
|
USER="root"
|
||||||
|
|
||||||
|
|
||||||
# Create CARGO_HOME folder and don't download rust docs
|
# Create CARGO_HOME folder and don't download rust docs
|
||||||
RUN mkdir -pv "${CARGO_HOME}" \
|
RUN mkdir -pv "${CARGO_HOME}" \
|
||||||
&& rustup set profile minimal
|
&& rustup set profile minimal
|
||||||
|
|
||||||
#
|
# Install build dependencies for the arm64 architecture
|
||||||
# Install required build libs for arm64 architecture.
|
|
||||||
# hadolint ignore=DL3059
|
|
||||||
RUN dpkg --add-architecture arm64 \
|
RUN dpkg --add-architecture arm64 \
|
||||||
&& apt-get update \
|
&& apt-get update \
|
||||||
&& apt-get install -y \
|
&& apt-get install -y \
|
||||||
--no-install-recommends \
|
--no-install-recommends \
|
||||||
libssl-dev:arm64 \
|
gcc-aarch64-linux-gnu \
|
||||||
libc6-dev:arm64 \
|
libc6-dev:arm64 \
|
||||||
libpq5:arm64 \
|
libcap2-bin \
|
||||||
libpq-dev:arm64 \
|
|
||||||
libmariadb3:arm64 \
|
|
||||||
libmariadb-dev:arm64 \
|
libmariadb-dev:arm64 \
|
||||||
libmariadb-dev-compat:arm64 \
|
libmariadb-dev-compat:arm64 \
|
||||||
gcc-aarch64-linux-gnu \
|
libmariadb3:arm64 \
|
||||||
|
libpq-dev:arm64 \
|
||||||
|
libpq5:arm64 \
|
||||||
|
libssl-dev:arm64 \
|
||||||
#
|
#
|
||||||
# Make sure cargo has the right target config
|
# Make sure cargo has the right target config
|
||||||
&& echo '[target.aarch64-unknown-linux-gnu]' >> "${CARGO_HOME}/config" \
|
&& echo '[target.aarch64-unknown-linux-gnu]' >> "${CARGO_HOME}/config" \
|
||||||
|
@ -71,7 +67,6 @@ ENV CC_aarch64_unknown_linux_gnu="/usr/bin/aarch64-linux-gnu-gcc" \
|
||||||
OPENSSL_INCLUDE_DIR="/usr/include/aarch64-linux-gnu" \
|
OPENSSL_INCLUDE_DIR="/usr/include/aarch64-linux-gnu" \
|
||||||
OPENSSL_LIB_DIR="/usr/lib/aarch64-linux-gnu"
|
OPENSSL_LIB_DIR="/usr/lib/aarch64-linux-gnu"
|
||||||
|
|
||||||
|
|
||||||
# Creates a dummy project used to grab dependencies
|
# Creates a dummy project used to grab dependencies
|
||||||
RUN USER=root cargo new --bin /app
|
RUN USER=root cargo new --bin /app
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
@ -101,9 +96,9 @@ RUN touch src/main.rs
|
||||||
|
|
||||||
# Builds again, this time it'll just be
|
# Builds again, this time it'll just be
|
||||||
# your actual source files being built
|
# your actual source files being built
|
||||||
# hadolint ignore=DL3059
|
|
||||||
RUN cargo build --features ${DB} --release --target=aarch64-unknown-linux-gnu
|
RUN cargo build --features ${DB} --release --target=aarch64-unknown-linux-gnu
|
||||||
|
|
||||||
|
|
||||||
######################## RUNTIME IMAGE ########################
|
######################## RUNTIME IMAGE ########################
|
||||||
# Create a new stage with a minimal image
|
# Create a new stage with a minimal image
|
||||||
# because we already have a binary built
|
# because we already have a binary built
|
||||||
|
@ -113,22 +108,20 @@ ENV ROCKET_PROFILE="release" \
|
||||||
ROCKET_ADDRESS=0.0.0.0 \
|
ROCKET_ADDRESS=0.0.0.0 \
|
||||||
ROCKET_PORT=80
|
ROCKET_PORT=80
|
||||||
|
|
||||||
# hadolint ignore=DL3059
|
|
||||||
RUN [ "cross-build-start" ]
|
RUN [ "cross-build-start" ]
|
||||||
|
|
||||||
# Create data folder and Install needed libraries
|
# Create data folder and Install needed libraries
|
||||||
RUN mkdir /data \
|
RUN mkdir /data \
|
||||||
&& apt-get update && apt-get install -y \
|
&& apt-get update && apt-get install -y \
|
||||||
--no-install-recommends \
|
--no-install-recommends \
|
||||||
openssl \
|
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
curl \
|
curl \
|
||||||
libmariadb-dev-compat \
|
libmariadb-dev-compat \
|
||||||
libpq5 \
|
libpq5 \
|
||||||
|
openssl \
|
||||||
&& apt-get clean \
|
&& apt-get clean \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
# hadolint ignore=DL3059
|
|
||||||
RUN [ "cross-build-end" ]
|
RUN [ "cross-build-end" ]
|
||||||
|
|
||||||
VOLUME /data
|
VOLUME /data
|
||||||
|
|
|
@ -29,8 +29,6 @@ FROM vaultwarden/web-vault@sha256:d5f71fb05c4b87935bf51d84140db0f8716cabfe2974fb
|
||||||
########################## BUILD IMAGE ##########################
|
########################## BUILD IMAGE ##########################
|
||||||
FROM blackdex/rust-musl:aarch64-musl-stable-1.66.1 as build
|
FROM blackdex/rust-musl:aarch64-musl-stable-1.66.1 as build
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Build time options to avoid dpkg warnings and help with reproducible builds.
|
# Build time options to avoid dpkg warnings and help with reproducible builds.
|
||||||
ENV DEBIAN_FRONTEND=noninteractive \
|
ENV DEBIAN_FRONTEND=noninteractive \
|
||||||
LANG=C.UTF-8 \
|
LANG=C.UTF-8 \
|
||||||
|
@ -39,7 +37,6 @@ ENV DEBIAN_FRONTEND=noninteractive \
|
||||||
CARGO_HOME="/root/.cargo" \
|
CARGO_HOME="/root/.cargo" \
|
||||||
USER="root"
|
USER="root"
|
||||||
|
|
||||||
|
|
||||||
# Create CARGO_HOME folder and don't download rust docs
|
# Create CARGO_HOME folder and don't download rust docs
|
||||||
RUN mkdir -pv "${CARGO_HOME}" \
|
RUN mkdir -pv "${CARGO_HOME}" \
|
||||||
&& rustup set profile minimal
|
&& rustup set profile minimal
|
||||||
|
@ -75,9 +72,9 @@ RUN touch src/main.rs
|
||||||
|
|
||||||
# Builds again, this time it'll just be
|
# Builds again, this time it'll just be
|
||||||
# your actual source files being built
|
# your actual source files being built
|
||||||
# hadolint ignore=DL3059
|
|
||||||
RUN cargo build --features ${DB} --release --target=aarch64-unknown-linux-musl
|
RUN cargo build --features ${DB} --release --target=aarch64-unknown-linux-musl
|
||||||
|
|
||||||
|
|
||||||
######################## RUNTIME IMAGE ########################
|
######################## RUNTIME IMAGE ########################
|
||||||
# Create a new stage with a minimal image
|
# Create a new stage with a minimal image
|
||||||
# because we already have a binary built
|
# because we already have a binary built
|
||||||
|
@ -89,18 +86,16 @@ ENV ROCKET_PROFILE="release" \
|
||||||
SSL_CERT_DIR=/etc/ssl/certs
|
SSL_CERT_DIR=/etc/ssl/certs
|
||||||
|
|
||||||
|
|
||||||
# hadolint ignore=DL3059
|
|
||||||
RUN [ "cross-build-start" ]
|
RUN [ "cross-build-start" ]
|
||||||
|
|
||||||
# Create data folder and Install needed libraries
|
# Create data folder and Install needed libraries
|
||||||
RUN mkdir /data \
|
RUN mkdir /data \
|
||||||
&& apk add --no-cache \
|
&& apk add --no-cache \
|
||||||
openssl \
|
ca-certificates \
|
||||||
tzdata \
|
|
||||||
curl \
|
curl \
|
||||||
ca-certificates
|
openssl \
|
||||||
|
tzdata
|
||||||
|
|
||||||
# hadolint ignore=DL3059
|
|
||||||
RUN [ "cross-build-end" ]
|
RUN [ "cross-build-end" ]
|
||||||
|
|
||||||
VOLUME /data
|
VOLUME /data
|
||||||
|
|
|
@ -29,8 +29,6 @@ FROM vaultwarden/web-vault@sha256:d5f71fb05c4b87935bf51d84140db0f8716cabfe2974fb
|
||||||
########################## BUILD IMAGE ##########################
|
########################## BUILD IMAGE ##########################
|
||||||
FROM rust:1.66-bullseye as build
|
FROM rust:1.66-bullseye as build
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Build time options to avoid dpkg warnings and help with reproducible builds.
|
# Build time options to avoid dpkg warnings and help with reproducible builds.
|
||||||
ENV DEBIAN_FRONTEND=noninteractive \
|
ENV DEBIAN_FRONTEND=noninteractive \
|
||||||
LANG=C.UTF-8 \
|
LANG=C.UTF-8 \
|
||||||
|
@ -39,26 +37,24 @@ ENV DEBIAN_FRONTEND=noninteractive \
|
||||||
CARGO_HOME="/root/.cargo" \
|
CARGO_HOME="/root/.cargo" \
|
||||||
USER="root"
|
USER="root"
|
||||||
|
|
||||||
|
|
||||||
# Create CARGO_HOME folder and don't download rust docs
|
# Create CARGO_HOME folder and don't download rust docs
|
||||||
RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/.cargo/registry mkdir -pv "${CARGO_HOME}" \
|
RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/.cargo/registry mkdir -pv "${CARGO_HOME}" \
|
||||||
&& rustup set profile minimal
|
&& rustup set profile minimal
|
||||||
|
|
||||||
#
|
# Install build dependencies for the arm64 architecture
|
||||||
# Install required build libs for arm64 architecture.
|
|
||||||
# hadolint ignore=DL3059
|
|
||||||
RUN dpkg --add-architecture arm64 \
|
RUN dpkg --add-architecture arm64 \
|
||||||
&& apt-get update \
|
&& apt-get update \
|
||||||
&& apt-get install -y \
|
&& apt-get install -y \
|
||||||
--no-install-recommends \
|
--no-install-recommends \
|
||||||
libssl-dev:arm64 \
|
gcc-aarch64-linux-gnu \
|
||||||
libc6-dev:arm64 \
|
libc6-dev:arm64 \
|
||||||
libpq5:arm64 \
|
libcap2-bin \
|
||||||
libpq-dev:arm64 \
|
|
||||||
libmariadb3:arm64 \
|
|
||||||
libmariadb-dev:arm64 \
|
libmariadb-dev:arm64 \
|
||||||
libmariadb-dev-compat:arm64 \
|
libmariadb-dev-compat:arm64 \
|
||||||
gcc-aarch64-linux-gnu \
|
libmariadb3:arm64 \
|
||||||
|
libpq-dev:arm64 \
|
||||||
|
libpq5:arm64 \
|
||||||
|
libssl-dev:arm64 \
|
||||||
#
|
#
|
||||||
# Make sure cargo has the right target config
|
# Make sure cargo has the right target config
|
||||||
&& echo '[target.aarch64-unknown-linux-gnu]' >> "${CARGO_HOME}/config" \
|
&& echo '[target.aarch64-unknown-linux-gnu]' >> "${CARGO_HOME}/config" \
|
||||||
|
@ -71,7 +67,6 @@ ENV CC_aarch64_unknown_linux_gnu="/usr/bin/aarch64-linux-gnu-gcc" \
|
||||||
OPENSSL_INCLUDE_DIR="/usr/include/aarch64-linux-gnu" \
|
OPENSSL_INCLUDE_DIR="/usr/include/aarch64-linux-gnu" \
|
||||||
OPENSSL_LIB_DIR="/usr/lib/aarch64-linux-gnu"
|
OPENSSL_LIB_DIR="/usr/lib/aarch64-linux-gnu"
|
||||||
|
|
||||||
|
|
||||||
# Creates a dummy project used to grab dependencies
|
# Creates a dummy project used to grab dependencies
|
||||||
RUN USER=root cargo new --bin /app
|
RUN USER=root cargo new --bin /app
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
@ -101,9 +96,14 @@ RUN touch src/main.rs
|
||||||
|
|
||||||
# Builds again, this time it'll just be
|
# Builds again, this time it'll just be
|
||||||
# your actual source files being built
|
# your actual source files being built
|
||||||
# hadolint ignore=DL3059
|
|
||||||
RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/.cargo/registry cargo build --features ${DB} --release --target=aarch64-unknown-linux-gnu
|
RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/.cargo/registry cargo build --features ${DB} --release --target=aarch64-unknown-linux-gnu
|
||||||
|
|
||||||
|
# Add the `cap_net_bind_service` capability to allow listening on
|
||||||
|
# privileged (< 1024) ports even when running as a non-root user.
|
||||||
|
# This is only done if building with BuildKit; with the legacy
|
||||||
|
# builder, the `COPY` instruction doesn't carry over capabilities.
|
||||||
|
RUN setcap cap_net_bind_service=+ep target/aarch64-unknown-linux-gnu/release/vaultwarden
|
||||||
|
|
||||||
######################## RUNTIME IMAGE ########################
|
######################## RUNTIME IMAGE ########################
|
||||||
# Create a new stage with a minimal image
|
# Create a new stage with a minimal image
|
||||||
# because we already have a binary built
|
# because we already have a binary built
|
||||||
|
@ -113,22 +113,20 @@ ENV ROCKET_PROFILE="release" \
|
||||||
ROCKET_ADDRESS=0.0.0.0 \
|
ROCKET_ADDRESS=0.0.0.0 \
|
||||||
ROCKET_PORT=80
|
ROCKET_PORT=80
|
||||||
|
|
||||||
# hadolint ignore=DL3059
|
|
||||||
RUN [ "cross-build-start" ]
|
RUN [ "cross-build-start" ]
|
||||||
|
|
||||||
# Create data folder and Install needed libraries
|
# Create data folder and Install needed libraries
|
||||||
RUN mkdir /data \
|
RUN mkdir /data \
|
||||||
&& apt-get update && apt-get install -y \
|
&& apt-get update && apt-get install -y \
|
||||||
--no-install-recommends \
|
--no-install-recommends \
|
||||||
openssl \
|
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
curl \
|
curl \
|
||||||
libmariadb-dev-compat \
|
libmariadb-dev-compat \
|
||||||
libpq5 \
|
libpq5 \
|
||||||
|
openssl \
|
||||||
&& apt-get clean \
|
&& apt-get clean \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
# hadolint ignore=DL3059
|
|
||||||
RUN [ "cross-build-end" ]
|
RUN [ "cross-build-end" ]
|
||||||
|
|
||||||
VOLUME /data
|
VOLUME /data
|
|
@ -29,8 +29,6 @@ FROM vaultwarden/web-vault@sha256:d5f71fb05c4b87935bf51d84140db0f8716cabfe2974fb
|
||||||
########################## BUILD IMAGE ##########################
|
########################## BUILD IMAGE ##########################
|
||||||
FROM blackdex/rust-musl:aarch64-musl-stable-1.66.1 as build
|
FROM blackdex/rust-musl:aarch64-musl-stable-1.66.1 as build
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Build time options to avoid dpkg warnings and help with reproducible builds.
|
# Build time options to avoid dpkg warnings and help with reproducible builds.
|
||||||
ENV DEBIAN_FRONTEND=noninteractive \
|
ENV DEBIAN_FRONTEND=noninteractive \
|
||||||
LANG=C.UTF-8 \
|
LANG=C.UTF-8 \
|
||||||
|
@ -39,7 +37,6 @@ ENV DEBIAN_FRONTEND=noninteractive \
|
||||||
CARGO_HOME="/root/.cargo" \
|
CARGO_HOME="/root/.cargo" \
|
||||||
USER="root"
|
USER="root"
|
||||||
|
|
||||||
|
|
||||||
# Create CARGO_HOME folder and don't download rust docs
|
# Create CARGO_HOME folder and don't download rust docs
|
||||||
RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/.cargo/registry mkdir -pv "${CARGO_HOME}" \
|
RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/.cargo/registry mkdir -pv "${CARGO_HOME}" \
|
||||||
&& rustup set profile minimal
|
&& rustup set profile minimal
|
||||||
|
@ -75,9 +72,14 @@ RUN touch src/main.rs
|
||||||
|
|
||||||
# Builds again, this time it'll just be
|
# Builds again, this time it'll just be
|
||||||
# your actual source files being built
|
# your actual source files being built
|
||||||
# hadolint ignore=DL3059
|
|
||||||
RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/.cargo/registry cargo build --features ${DB} --release --target=aarch64-unknown-linux-musl
|
RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/.cargo/registry cargo build --features ${DB} --release --target=aarch64-unknown-linux-musl
|
||||||
|
|
||||||
|
# Add the `cap_net_bind_service` capability to allow listening on
|
||||||
|
# privileged (< 1024) ports even when running as a non-root user.
|
||||||
|
# This is only done if building with BuildKit; with the legacy
|
||||||
|
# builder, the `COPY` instruction doesn't carry over capabilities.
|
||||||
|
RUN setcap cap_net_bind_service=+ep target/aarch64-unknown-linux-musl/release/vaultwarden
|
||||||
|
|
||||||
######################## RUNTIME IMAGE ########################
|
######################## RUNTIME IMAGE ########################
|
||||||
# Create a new stage with a minimal image
|
# Create a new stage with a minimal image
|
||||||
# because we already have a binary built
|
# because we already have a binary built
|
||||||
|
@ -89,18 +91,16 @@ ENV ROCKET_PROFILE="release" \
|
||||||
SSL_CERT_DIR=/etc/ssl/certs
|
SSL_CERT_DIR=/etc/ssl/certs
|
||||||
|
|
||||||
|
|
||||||
# hadolint ignore=DL3059
|
|
||||||
RUN [ "cross-build-start" ]
|
RUN [ "cross-build-start" ]
|
||||||
|
|
||||||
# Create data folder and Install needed libraries
|
# Create data folder and Install needed libraries
|
||||||
RUN mkdir /data \
|
RUN mkdir /data \
|
||||||
&& apk add --no-cache \
|
&& apk add --no-cache \
|
||||||
openssl \
|
ca-certificates \
|
||||||
tzdata \
|
|
||||||
curl \
|
curl \
|
||||||
ca-certificates
|
openssl \
|
||||||
|
tzdata
|
||||||
|
|
||||||
# hadolint ignore=DL3059
|
|
||||||
RUN [ "cross-build-end" ]
|
RUN [ "cross-build-end" ]
|
||||||
|
|
||||||
VOLUME /data
|
VOLUME /data
|
|
@ -29,8 +29,6 @@ FROM vaultwarden/web-vault@sha256:d5f71fb05c4b87935bf51d84140db0f8716cabfe2974fb
|
||||||
########################## BUILD IMAGE ##########################
|
########################## BUILD IMAGE ##########################
|
||||||
FROM rust:1.66-bullseye as build
|
FROM rust:1.66-bullseye as build
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Build time options to avoid dpkg warnings and help with reproducible builds.
|
# Build time options to avoid dpkg warnings and help with reproducible builds.
|
||||||
ENV DEBIAN_FRONTEND=noninteractive \
|
ENV DEBIAN_FRONTEND=noninteractive \
|
||||||
LANG=C.UTF-8 \
|
LANG=C.UTF-8 \
|
||||||
|
@ -39,26 +37,24 @@ ENV DEBIAN_FRONTEND=noninteractive \
|
||||||
CARGO_HOME="/root/.cargo" \
|
CARGO_HOME="/root/.cargo" \
|
||||||
USER="root"
|
USER="root"
|
||||||
|
|
||||||
|
|
||||||
# Create CARGO_HOME folder and don't download rust docs
|
# Create CARGO_HOME folder and don't download rust docs
|
||||||
RUN mkdir -pv "${CARGO_HOME}" \
|
RUN mkdir -pv "${CARGO_HOME}" \
|
||||||
&& rustup set profile minimal
|
&& rustup set profile minimal
|
||||||
|
|
||||||
#
|
# Install build dependencies for the armel architecture
|
||||||
# Install required build libs for armel architecture.
|
|
||||||
# hadolint ignore=DL3059
|
|
||||||
RUN dpkg --add-architecture armel \
|
RUN dpkg --add-architecture armel \
|
||||||
&& apt-get update \
|
&& apt-get update \
|
||||||
&& apt-get install -y \
|
&& apt-get install -y \
|
||||||
--no-install-recommends \
|
--no-install-recommends \
|
||||||
libssl-dev:armel \
|
gcc-arm-linux-gnueabi \
|
||||||
libc6-dev:armel \
|
libc6-dev:armel \
|
||||||
libpq5:armel \
|
libcap2-bin \
|
||||||
libpq-dev:armel \
|
|
||||||
libmariadb3:armel \
|
|
||||||
libmariadb-dev:armel \
|
libmariadb-dev:armel \
|
||||||
libmariadb-dev-compat:armel \
|
libmariadb-dev-compat:armel \
|
||||||
gcc-arm-linux-gnueabi \
|
libmariadb3:armel \
|
||||||
|
libpq-dev:armel \
|
||||||
|
libpq5:armel \
|
||||||
|
libssl-dev:armel \
|
||||||
#
|
#
|
||||||
# Make sure cargo has the right target config
|
# Make sure cargo has the right target config
|
||||||
&& echo '[target.arm-unknown-linux-gnueabi]' >> "${CARGO_HOME}/config" \
|
&& echo '[target.arm-unknown-linux-gnueabi]' >> "${CARGO_HOME}/config" \
|
||||||
|
@ -71,7 +67,6 @@ ENV CC_arm_unknown_linux_gnueabi="/usr/bin/arm-linux-gnueabi-gcc" \
|
||||||
OPENSSL_INCLUDE_DIR="/usr/include/arm-linux-gnueabi" \
|
OPENSSL_INCLUDE_DIR="/usr/include/arm-linux-gnueabi" \
|
||||||
OPENSSL_LIB_DIR="/usr/lib/arm-linux-gnueabi"
|
OPENSSL_LIB_DIR="/usr/lib/arm-linux-gnueabi"
|
||||||
|
|
||||||
|
|
||||||
# Creates a dummy project used to grab dependencies
|
# Creates a dummy project used to grab dependencies
|
||||||
RUN USER=root cargo new --bin /app
|
RUN USER=root cargo new --bin /app
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
@ -101,9 +96,9 @@ RUN touch src/main.rs
|
||||||
|
|
||||||
# Builds again, this time it'll just be
|
# Builds again, this time it'll just be
|
||||||
# your actual source files being built
|
# your actual source files being built
|
||||||
# hadolint ignore=DL3059
|
|
||||||
RUN cargo build --features ${DB} --release --target=arm-unknown-linux-gnueabi
|
RUN cargo build --features ${DB} --release --target=arm-unknown-linux-gnueabi
|
||||||
|
|
||||||
|
|
||||||
######################## RUNTIME IMAGE ########################
|
######################## RUNTIME IMAGE ########################
|
||||||
# Create a new stage with a minimal image
|
# Create a new stage with a minimal image
|
||||||
# because we already have a binary built
|
# because we already have a binary built
|
||||||
|
@ -113,27 +108,24 @@ ENV ROCKET_PROFILE="release" \
|
||||||
ROCKET_ADDRESS=0.0.0.0 \
|
ROCKET_ADDRESS=0.0.0.0 \
|
||||||
ROCKET_PORT=80
|
ROCKET_PORT=80
|
||||||
|
|
||||||
# hadolint ignore=DL3059
|
|
||||||
RUN [ "cross-build-start" ]
|
RUN [ "cross-build-start" ]
|
||||||
|
|
||||||
# Create data folder and Install needed libraries
|
# Create data folder and Install needed libraries
|
||||||
RUN mkdir /data \
|
RUN mkdir /data \
|
||||||
&& apt-get update && apt-get install -y \
|
&& apt-get update && apt-get install -y \
|
||||||
--no-install-recommends \
|
--no-install-recommends \
|
||||||
openssl \
|
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
curl \
|
curl \
|
||||||
libmariadb-dev-compat \
|
libmariadb-dev-compat \
|
||||||
libpq5 \
|
libpq5 \
|
||||||
|
openssl \
|
||||||
&& apt-get clean \
|
&& apt-get clean \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
# In the Balena Bullseye images for armv6/rpi-debian there is a missing symlink.
|
# In the Balena Bullseye images for armv6/rpi-debian there is a missing symlink.
|
||||||
# This symlink was there in the buster images, and for some reason this is needed.
|
# This symlink was there in the buster images, and for some reason this is needed.
|
||||||
# hadolint ignore=DL3059
|
|
||||||
RUN ln -v -s /lib/ld-linux-armhf.so.3 /lib/ld-linux.so.3
|
RUN ln -v -s /lib/ld-linux-armhf.so.3 /lib/ld-linux.so.3
|
||||||
|
|
||||||
# hadolint ignore=DL3059
|
|
||||||
RUN [ "cross-build-end" ]
|
RUN [ "cross-build-end" ]
|
||||||
|
|
||||||
VOLUME /data
|
VOLUME /data
|
||||||
|
|
|
@ -29,8 +29,6 @@ FROM vaultwarden/web-vault@sha256:d5f71fb05c4b87935bf51d84140db0f8716cabfe2974fb
|
||||||
########################## BUILD IMAGE ##########################
|
########################## BUILD IMAGE ##########################
|
||||||
FROM blackdex/rust-musl:arm-musleabi-stable-1.66.1 as build
|
FROM blackdex/rust-musl:arm-musleabi-stable-1.66.1 as build
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Build time options to avoid dpkg warnings and help with reproducible builds.
|
# Build time options to avoid dpkg warnings and help with reproducible builds.
|
||||||
ENV DEBIAN_FRONTEND=noninteractive \
|
ENV DEBIAN_FRONTEND=noninteractive \
|
||||||
LANG=C.UTF-8 \
|
LANG=C.UTF-8 \
|
||||||
|
@ -39,7 +37,6 @@ ENV DEBIAN_FRONTEND=noninteractive \
|
||||||
CARGO_HOME="/root/.cargo" \
|
CARGO_HOME="/root/.cargo" \
|
||||||
USER="root"
|
USER="root"
|
||||||
|
|
||||||
|
|
||||||
# Create CARGO_HOME folder and don't download rust docs
|
# Create CARGO_HOME folder and don't download rust docs
|
||||||
RUN mkdir -pv "${CARGO_HOME}" \
|
RUN mkdir -pv "${CARGO_HOME}" \
|
||||||
&& rustup set profile minimal
|
&& rustup set profile minimal
|
||||||
|
@ -77,9 +74,9 @@ RUN touch src/main.rs
|
||||||
|
|
||||||
# Builds again, this time it'll just be
|
# Builds again, this time it'll just be
|
||||||
# your actual source files being built
|
# your actual source files being built
|
||||||
# hadolint ignore=DL3059
|
|
||||||
RUN cargo build --features ${DB} --release --target=arm-unknown-linux-musleabi
|
RUN cargo build --features ${DB} --release --target=arm-unknown-linux-musleabi
|
||||||
|
|
||||||
|
|
||||||
######################## RUNTIME IMAGE ########################
|
######################## RUNTIME IMAGE ########################
|
||||||
# Create a new stage with a minimal image
|
# Create a new stage with a minimal image
|
||||||
# because we already have a binary built
|
# because we already have a binary built
|
||||||
|
@ -91,18 +88,16 @@ ENV ROCKET_PROFILE="release" \
|
||||||
SSL_CERT_DIR=/etc/ssl/certs
|
SSL_CERT_DIR=/etc/ssl/certs
|
||||||
|
|
||||||
|
|
||||||
# hadolint ignore=DL3059
|
|
||||||
RUN [ "cross-build-start" ]
|
RUN [ "cross-build-start" ]
|
||||||
|
|
||||||
# Create data folder and Install needed libraries
|
# Create data folder and Install needed libraries
|
||||||
RUN mkdir /data \
|
RUN mkdir /data \
|
||||||
&& apk add --no-cache \
|
&& apk add --no-cache \
|
||||||
openssl \
|
ca-certificates \
|
||||||
tzdata \
|
|
||||||
curl \
|
curl \
|
||||||
ca-certificates
|
openssl \
|
||||||
|
tzdata
|
||||||
|
|
||||||
# hadolint ignore=DL3059
|
|
||||||
RUN [ "cross-build-end" ]
|
RUN [ "cross-build-end" ]
|
||||||
|
|
||||||
VOLUME /data
|
VOLUME /data
|
||||||
|
|
|
@ -29,8 +29,6 @@ FROM vaultwarden/web-vault@sha256:d5f71fb05c4b87935bf51d84140db0f8716cabfe2974fb
|
||||||
########################## BUILD IMAGE ##########################
|
########################## BUILD IMAGE ##########################
|
||||||
FROM rust:1.66-bullseye as build
|
FROM rust:1.66-bullseye as build
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Build time options to avoid dpkg warnings and help with reproducible builds.
|
# Build time options to avoid dpkg warnings and help with reproducible builds.
|
||||||
ENV DEBIAN_FRONTEND=noninteractive \
|
ENV DEBIAN_FRONTEND=noninteractive \
|
||||||
LANG=C.UTF-8 \
|
LANG=C.UTF-8 \
|
||||||
|
@ -39,26 +37,24 @@ ENV DEBIAN_FRONTEND=noninteractive \
|
||||||
CARGO_HOME="/root/.cargo" \
|
CARGO_HOME="/root/.cargo" \
|
||||||
USER="root"
|
USER="root"
|
||||||
|
|
||||||
|
|
||||||
# Create CARGO_HOME folder and don't download rust docs
|
# Create CARGO_HOME folder and don't download rust docs
|
||||||
RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/.cargo/registry mkdir -pv "${CARGO_HOME}" \
|
RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/.cargo/registry mkdir -pv "${CARGO_HOME}" \
|
||||||
&& rustup set profile minimal
|
&& rustup set profile minimal
|
||||||
|
|
||||||
#
|
# Install build dependencies for the armel architecture
|
||||||
# Install required build libs for armel architecture.
|
|
||||||
# hadolint ignore=DL3059
|
|
||||||
RUN dpkg --add-architecture armel \
|
RUN dpkg --add-architecture armel \
|
||||||
&& apt-get update \
|
&& apt-get update \
|
||||||
&& apt-get install -y \
|
&& apt-get install -y \
|
||||||
--no-install-recommends \
|
--no-install-recommends \
|
||||||
libssl-dev:armel \
|
gcc-arm-linux-gnueabi \
|
||||||
libc6-dev:armel \
|
libc6-dev:armel \
|
||||||
libpq5:armel \
|
libcap2-bin \
|
||||||
libpq-dev:armel \
|
|
||||||
libmariadb3:armel \
|
|
||||||
libmariadb-dev:armel \
|
libmariadb-dev:armel \
|
||||||
libmariadb-dev-compat:armel \
|
libmariadb-dev-compat:armel \
|
||||||
gcc-arm-linux-gnueabi \
|
libmariadb3:armel \
|
||||||
|
libpq-dev:armel \
|
||||||
|
libpq5:armel \
|
||||||
|
libssl-dev:armel \
|
||||||
#
|
#
|
||||||
# Make sure cargo has the right target config
|
# Make sure cargo has the right target config
|
||||||
&& echo '[target.arm-unknown-linux-gnueabi]' >> "${CARGO_HOME}/config" \
|
&& echo '[target.arm-unknown-linux-gnueabi]' >> "${CARGO_HOME}/config" \
|
||||||
|
@ -71,7 +67,6 @@ ENV CC_arm_unknown_linux_gnueabi="/usr/bin/arm-linux-gnueabi-gcc" \
|
||||||
OPENSSL_INCLUDE_DIR="/usr/include/arm-linux-gnueabi" \
|
OPENSSL_INCLUDE_DIR="/usr/include/arm-linux-gnueabi" \
|
||||||
OPENSSL_LIB_DIR="/usr/lib/arm-linux-gnueabi"
|
OPENSSL_LIB_DIR="/usr/lib/arm-linux-gnueabi"
|
||||||
|
|
||||||
|
|
||||||
# Creates a dummy project used to grab dependencies
|
# Creates a dummy project used to grab dependencies
|
||||||
RUN USER=root cargo new --bin /app
|
RUN USER=root cargo new --bin /app
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
@ -101,9 +96,14 @@ RUN touch src/main.rs
|
||||||
|
|
||||||
# Builds again, this time it'll just be
|
# Builds again, this time it'll just be
|
||||||
# your actual source files being built
|
# your actual source files being built
|
||||||
# hadolint ignore=DL3059
|
|
||||||
RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/.cargo/registry cargo build --features ${DB} --release --target=arm-unknown-linux-gnueabi
|
RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/.cargo/registry cargo build --features ${DB} --release --target=arm-unknown-linux-gnueabi
|
||||||
|
|
||||||
|
# Add the `cap_net_bind_service` capability to allow listening on
|
||||||
|
# privileged (< 1024) ports even when running as a non-root user.
|
||||||
|
# This is only done if building with BuildKit; with the legacy
|
||||||
|
# builder, the `COPY` instruction doesn't carry over capabilities.
|
||||||
|
RUN setcap cap_net_bind_service=+ep target/arm-unknown-linux-gnueabi/release/vaultwarden
|
||||||
|
|
||||||
######################## RUNTIME IMAGE ########################
|
######################## RUNTIME IMAGE ########################
|
||||||
# Create a new stage with a minimal image
|
# Create a new stage with a minimal image
|
||||||
# because we already have a binary built
|
# because we already have a binary built
|
||||||
|
@ -113,27 +113,24 @@ ENV ROCKET_PROFILE="release" \
|
||||||
ROCKET_ADDRESS=0.0.0.0 \
|
ROCKET_ADDRESS=0.0.0.0 \
|
||||||
ROCKET_PORT=80
|
ROCKET_PORT=80
|
||||||
|
|
||||||
# hadolint ignore=DL3059
|
|
||||||
RUN [ "cross-build-start" ]
|
RUN [ "cross-build-start" ]
|
||||||
|
|
||||||
# Create data folder and Install needed libraries
|
# Create data folder and Install needed libraries
|
||||||
RUN mkdir /data \
|
RUN mkdir /data \
|
||||||
&& apt-get update && apt-get install -y \
|
&& apt-get update && apt-get install -y \
|
||||||
--no-install-recommends \
|
--no-install-recommends \
|
||||||
openssl \
|
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
curl \
|
curl \
|
||||||
libmariadb-dev-compat \
|
libmariadb-dev-compat \
|
||||||
libpq5 \
|
libpq5 \
|
||||||
|
openssl \
|
||||||
&& apt-get clean \
|
&& apt-get clean \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
# In the Balena Bullseye images for armv6/rpi-debian there is a missing symlink.
|
# In the Balena Bullseye images for armv6/rpi-debian there is a missing symlink.
|
||||||
# This symlink was there in the buster images, and for some reason this is needed.
|
# This symlink was there in the buster images, and for some reason this is needed.
|
||||||
# hadolint ignore=DL3059
|
|
||||||
RUN ln -v -s /lib/ld-linux-armhf.so.3 /lib/ld-linux.so.3
|
RUN ln -v -s /lib/ld-linux-armhf.so.3 /lib/ld-linux.so.3
|
||||||
|
|
||||||
# hadolint ignore=DL3059
|
|
||||||
RUN [ "cross-build-end" ]
|
RUN [ "cross-build-end" ]
|
||||||
|
|
||||||
VOLUME /data
|
VOLUME /data
|
|
@ -29,8 +29,6 @@ FROM vaultwarden/web-vault@sha256:d5f71fb05c4b87935bf51d84140db0f8716cabfe2974fb
|
||||||
########################## BUILD IMAGE ##########################
|
########################## BUILD IMAGE ##########################
|
||||||
FROM blackdex/rust-musl:arm-musleabi-stable-1.66.1 as build
|
FROM blackdex/rust-musl:arm-musleabi-stable-1.66.1 as build
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Build time options to avoid dpkg warnings and help with reproducible builds.
|
# Build time options to avoid dpkg warnings and help with reproducible builds.
|
||||||
ENV DEBIAN_FRONTEND=noninteractive \
|
ENV DEBIAN_FRONTEND=noninteractive \
|
||||||
LANG=C.UTF-8 \
|
LANG=C.UTF-8 \
|
||||||
|
@ -39,7 +37,6 @@ ENV DEBIAN_FRONTEND=noninteractive \
|
||||||
CARGO_HOME="/root/.cargo" \
|
CARGO_HOME="/root/.cargo" \
|
||||||
USER="root"
|
USER="root"
|
||||||
|
|
||||||
|
|
||||||
# Create CARGO_HOME folder and don't download rust docs
|
# Create CARGO_HOME folder and don't download rust docs
|
||||||
RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/.cargo/registry mkdir -pv "${CARGO_HOME}" \
|
RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/.cargo/registry mkdir -pv "${CARGO_HOME}" \
|
||||||
&& rustup set profile minimal
|
&& rustup set profile minimal
|
||||||
|
@ -77,9 +74,14 @@ RUN touch src/main.rs
|
||||||
|
|
||||||
# Builds again, this time it'll just be
|
# Builds again, this time it'll just be
|
||||||
# your actual source files being built
|
# your actual source files being built
|
||||||
# hadolint ignore=DL3059
|
|
||||||
RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/.cargo/registry cargo build --features ${DB} --release --target=arm-unknown-linux-musleabi
|
RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/.cargo/registry cargo build --features ${DB} --release --target=arm-unknown-linux-musleabi
|
||||||
|
|
||||||
|
# Add the `cap_net_bind_service` capability to allow listening on
|
||||||
|
# privileged (< 1024) ports even when running as a non-root user.
|
||||||
|
# This is only done if building with BuildKit; with the legacy
|
||||||
|
# builder, the `COPY` instruction doesn't carry over capabilities.
|
||||||
|
RUN setcap cap_net_bind_service=+ep target/arm-unknown-linux-musleabi/release/vaultwarden
|
||||||
|
|
||||||
######################## RUNTIME IMAGE ########################
|
######################## RUNTIME IMAGE ########################
|
||||||
# Create a new stage with a minimal image
|
# Create a new stage with a minimal image
|
||||||
# because we already have a binary built
|
# because we already have a binary built
|
||||||
|
@ -91,18 +93,16 @@ ENV ROCKET_PROFILE="release" \
|
||||||
SSL_CERT_DIR=/etc/ssl/certs
|
SSL_CERT_DIR=/etc/ssl/certs
|
||||||
|
|
||||||
|
|
||||||
# hadolint ignore=DL3059
|
|
||||||
RUN [ "cross-build-start" ]
|
RUN [ "cross-build-start" ]
|
||||||
|
|
||||||
# Create data folder and Install needed libraries
|
# Create data folder and Install needed libraries
|
||||||
RUN mkdir /data \
|
RUN mkdir /data \
|
||||||
&& apk add --no-cache \
|
&& apk add --no-cache \
|
||||||
openssl \
|
ca-certificates \
|
||||||
tzdata \
|
|
||||||
curl \
|
curl \
|
||||||
ca-certificates
|
openssl \
|
||||||
|
tzdata
|
||||||
|
|
||||||
# hadolint ignore=DL3059
|
|
||||||
RUN [ "cross-build-end" ]
|
RUN [ "cross-build-end" ]
|
||||||
|
|
||||||
VOLUME /data
|
VOLUME /data
|
|
@ -29,8 +29,6 @@ FROM vaultwarden/web-vault@sha256:d5f71fb05c4b87935bf51d84140db0f8716cabfe2974fb
|
||||||
########################## BUILD IMAGE ##########################
|
########################## BUILD IMAGE ##########################
|
||||||
FROM rust:1.66-bullseye as build
|
FROM rust:1.66-bullseye as build
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Build time options to avoid dpkg warnings and help with reproducible builds.
|
# Build time options to avoid dpkg warnings and help with reproducible builds.
|
||||||
ENV DEBIAN_FRONTEND=noninteractive \
|
ENV DEBIAN_FRONTEND=noninteractive \
|
||||||
LANG=C.UTF-8 \
|
LANG=C.UTF-8 \
|
||||||
|
@ -39,26 +37,24 @@ ENV DEBIAN_FRONTEND=noninteractive \
|
||||||
CARGO_HOME="/root/.cargo" \
|
CARGO_HOME="/root/.cargo" \
|
||||||
USER="root"
|
USER="root"
|
||||||
|
|
||||||
|
|
||||||
# Create CARGO_HOME folder and don't download rust docs
|
# Create CARGO_HOME folder and don't download rust docs
|
||||||
RUN mkdir -pv "${CARGO_HOME}" \
|
RUN mkdir -pv "${CARGO_HOME}" \
|
||||||
&& rustup set profile minimal
|
&& rustup set profile minimal
|
||||||
|
|
||||||
#
|
# Install build dependencies for the armhf architecture
|
||||||
# Install required build libs for armhf architecture.
|
|
||||||
# hadolint ignore=DL3059
|
|
||||||
RUN dpkg --add-architecture armhf \
|
RUN dpkg --add-architecture armhf \
|
||||||
&& apt-get update \
|
&& apt-get update \
|
||||||
&& apt-get install -y \
|
&& apt-get install -y \
|
||||||
--no-install-recommends \
|
--no-install-recommends \
|
||||||
libssl-dev:armhf \
|
gcc-arm-linux-gnueabihf \
|
||||||
libc6-dev:armhf \
|
libc6-dev:armhf \
|
||||||
libpq5:armhf \
|
libcap2-bin \
|
||||||
libpq-dev:armhf \
|
|
||||||
libmariadb3:armhf \
|
|
||||||
libmariadb-dev:armhf \
|
libmariadb-dev:armhf \
|
||||||
libmariadb-dev-compat:armhf \
|
libmariadb-dev-compat:armhf \
|
||||||
gcc-arm-linux-gnueabihf \
|
libmariadb3:armhf \
|
||||||
|
libpq-dev:armhf \
|
||||||
|
libpq5:armhf \
|
||||||
|
libssl-dev:armhf \
|
||||||
#
|
#
|
||||||
# Make sure cargo has the right target config
|
# Make sure cargo has the right target config
|
||||||
&& echo '[target.armv7-unknown-linux-gnueabihf]' >> "${CARGO_HOME}/config" \
|
&& echo '[target.armv7-unknown-linux-gnueabihf]' >> "${CARGO_HOME}/config" \
|
||||||
|
@ -71,7 +67,6 @@ ENV CC_armv7_unknown_linux_gnueabihf="/usr/bin/arm-linux-gnueabihf-gcc" \
|
||||||
OPENSSL_INCLUDE_DIR="/usr/include/arm-linux-gnueabihf" \
|
OPENSSL_INCLUDE_DIR="/usr/include/arm-linux-gnueabihf" \
|
||||||
OPENSSL_LIB_DIR="/usr/lib/arm-linux-gnueabihf"
|
OPENSSL_LIB_DIR="/usr/lib/arm-linux-gnueabihf"
|
||||||
|
|
||||||
|
|
||||||
# Creates a dummy project used to grab dependencies
|
# Creates a dummy project used to grab dependencies
|
||||||
RUN USER=root cargo new --bin /app
|
RUN USER=root cargo new --bin /app
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
@ -101,9 +96,9 @@ RUN touch src/main.rs
|
||||||
|
|
||||||
# Builds again, this time it'll just be
|
# Builds again, this time it'll just be
|
||||||
# your actual source files being built
|
# your actual source files being built
|
||||||
# hadolint ignore=DL3059
|
|
||||||
RUN cargo build --features ${DB} --release --target=armv7-unknown-linux-gnueabihf
|
RUN cargo build --features ${DB} --release --target=armv7-unknown-linux-gnueabihf
|
||||||
|
|
||||||
|
|
||||||
######################## RUNTIME IMAGE ########################
|
######################## RUNTIME IMAGE ########################
|
||||||
# Create a new stage with a minimal image
|
# Create a new stage with a minimal image
|
||||||
# because we already have a binary built
|
# because we already have a binary built
|
||||||
|
@ -113,22 +108,20 @@ ENV ROCKET_PROFILE="release" \
|
||||||
ROCKET_ADDRESS=0.0.0.0 \
|
ROCKET_ADDRESS=0.0.0.0 \
|
||||||
ROCKET_PORT=80
|
ROCKET_PORT=80
|
||||||
|
|
||||||
# hadolint ignore=DL3059
|
|
||||||
RUN [ "cross-build-start" ]
|
RUN [ "cross-build-start" ]
|
||||||
|
|
||||||
# Create data folder and Install needed libraries
|
# Create data folder and Install needed libraries
|
||||||
RUN mkdir /data \
|
RUN mkdir /data \
|
||||||
&& apt-get update && apt-get install -y \
|
&& apt-get update && apt-get install -y \
|
||||||
--no-install-recommends \
|
--no-install-recommends \
|
||||||
openssl \
|
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
curl \
|
curl \
|
||||||
libmariadb-dev-compat \
|
libmariadb-dev-compat \
|
||||||
libpq5 \
|
libpq5 \
|
||||||
|
openssl \
|
||||||
&& apt-get clean \
|
&& apt-get clean \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
# hadolint ignore=DL3059
|
|
||||||
RUN [ "cross-build-end" ]
|
RUN [ "cross-build-end" ]
|
||||||
|
|
||||||
VOLUME /data
|
VOLUME /data
|
||||||
|
|
|
@ -29,8 +29,6 @@ FROM vaultwarden/web-vault@sha256:d5f71fb05c4b87935bf51d84140db0f8716cabfe2974fb
|
||||||
########################## BUILD IMAGE ##########################
|
########################## BUILD IMAGE ##########################
|
||||||
FROM blackdex/rust-musl:armv7-musleabihf-stable-1.66.1 as build
|
FROM blackdex/rust-musl:armv7-musleabihf-stable-1.66.1 as build
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Build time options to avoid dpkg warnings and help with reproducible builds.
|
# Build time options to avoid dpkg warnings and help with reproducible builds.
|
||||||
ENV DEBIAN_FRONTEND=noninteractive \
|
ENV DEBIAN_FRONTEND=noninteractive \
|
||||||
LANG=C.UTF-8 \
|
LANG=C.UTF-8 \
|
||||||
|
@ -39,7 +37,6 @@ ENV DEBIAN_FRONTEND=noninteractive \
|
||||||
CARGO_HOME="/root/.cargo" \
|
CARGO_HOME="/root/.cargo" \
|
||||||
USER="root"
|
USER="root"
|
||||||
|
|
||||||
|
|
||||||
# Create CARGO_HOME folder and don't download rust docs
|
# Create CARGO_HOME folder and don't download rust docs
|
||||||
RUN mkdir -pv "${CARGO_HOME}" \
|
RUN mkdir -pv "${CARGO_HOME}" \
|
||||||
&& rustup set profile minimal
|
&& rustup set profile minimal
|
||||||
|
@ -75,9 +72,9 @@ RUN touch src/main.rs
|
||||||
|
|
||||||
# Builds again, this time it'll just be
|
# Builds again, this time it'll just be
|
||||||
# your actual source files being built
|
# your actual source files being built
|
||||||
# hadolint ignore=DL3059
|
|
||||||
RUN cargo build --features ${DB} --release --target=armv7-unknown-linux-musleabihf
|
RUN cargo build --features ${DB} --release --target=armv7-unknown-linux-musleabihf
|
||||||
|
|
||||||
|
|
||||||
######################## RUNTIME IMAGE ########################
|
######################## RUNTIME IMAGE ########################
|
||||||
# Create a new stage with a minimal image
|
# Create a new stage with a minimal image
|
||||||
# because we already have a binary built
|
# because we already have a binary built
|
||||||
|
@ -89,18 +86,16 @@ ENV ROCKET_PROFILE="release" \
|
||||||
SSL_CERT_DIR=/etc/ssl/certs
|
SSL_CERT_DIR=/etc/ssl/certs
|
||||||
|
|
||||||
|
|
||||||
# hadolint ignore=DL3059
|
|
||||||
RUN [ "cross-build-start" ]
|
RUN [ "cross-build-start" ]
|
||||||
|
|
||||||
# Create data folder and Install needed libraries
|
# Create data folder and Install needed libraries
|
||||||
RUN mkdir /data \
|
RUN mkdir /data \
|
||||||
&& apk add --no-cache \
|
&& apk add --no-cache \
|
||||||
openssl \
|
ca-certificates \
|
||||||
tzdata \
|
|
||||||
curl \
|
curl \
|
||||||
ca-certificates
|
openssl \
|
||||||
|
tzdata
|
||||||
|
|
||||||
# hadolint ignore=DL3059
|
|
||||||
RUN [ "cross-build-end" ]
|
RUN [ "cross-build-end" ]
|
||||||
|
|
||||||
VOLUME /data
|
VOLUME /data
|
||||||
|
|
|
@ -29,8 +29,6 @@ FROM vaultwarden/web-vault@sha256:d5f71fb05c4b87935bf51d84140db0f8716cabfe2974fb
|
||||||
########################## BUILD IMAGE ##########################
|
########################## BUILD IMAGE ##########################
|
||||||
FROM rust:1.66-bullseye as build
|
FROM rust:1.66-bullseye as build
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Build time options to avoid dpkg warnings and help with reproducible builds.
|
# Build time options to avoid dpkg warnings and help with reproducible builds.
|
||||||
ENV DEBIAN_FRONTEND=noninteractive \
|
ENV DEBIAN_FRONTEND=noninteractive \
|
||||||
LANG=C.UTF-8 \
|
LANG=C.UTF-8 \
|
||||||
|
@ -39,26 +37,24 @@ ENV DEBIAN_FRONTEND=noninteractive \
|
||||||
CARGO_HOME="/root/.cargo" \
|
CARGO_HOME="/root/.cargo" \
|
||||||
USER="root"
|
USER="root"
|
||||||
|
|
||||||
|
|
||||||
# Create CARGO_HOME folder and don't download rust docs
|
# Create CARGO_HOME folder and don't download rust docs
|
||||||
RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/.cargo/registry mkdir -pv "${CARGO_HOME}" \
|
RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/.cargo/registry mkdir -pv "${CARGO_HOME}" \
|
||||||
&& rustup set profile minimal
|
&& rustup set profile minimal
|
||||||
|
|
||||||
#
|
# Install build dependencies for the armhf architecture
|
||||||
# Install required build libs for armhf architecture.
|
|
||||||
# hadolint ignore=DL3059
|
|
||||||
RUN dpkg --add-architecture armhf \
|
RUN dpkg --add-architecture armhf \
|
||||||
&& apt-get update \
|
&& apt-get update \
|
||||||
&& apt-get install -y \
|
&& apt-get install -y \
|
||||||
--no-install-recommends \
|
--no-install-recommends \
|
||||||
libssl-dev:armhf \
|
gcc-arm-linux-gnueabihf \
|
||||||
libc6-dev:armhf \
|
libc6-dev:armhf \
|
||||||
libpq5:armhf \
|
libcap2-bin \
|
||||||
libpq-dev:armhf \
|
|
||||||
libmariadb3:armhf \
|
|
||||||
libmariadb-dev:armhf \
|
libmariadb-dev:armhf \
|
||||||
libmariadb-dev-compat:armhf \
|
libmariadb-dev-compat:armhf \
|
||||||
gcc-arm-linux-gnueabihf \
|
libmariadb3:armhf \
|
||||||
|
libpq-dev:armhf \
|
||||||
|
libpq5:armhf \
|
||||||
|
libssl-dev:armhf \
|
||||||
#
|
#
|
||||||
# Make sure cargo has the right target config
|
# Make sure cargo has the right target config
|
||||||
&& echo '[target.armv7-unknown-linux-gnueabihf]' >> "${CARGO_HOME}/config" \
|
&& echo '[target.armv7-unknown-linux-gnueabihf]' >> "${CARGO_HOME}/config" \
|
||||||
|
@ -71,7 +67,6 @@ ENV CC_armv7_unknown_linux_gnueabihf="/usr/bin/arm-linux-gnueabihf-gcc" \
|
||||||
OPENSSL_INCLUDE_DIR="/usr/include/arm-linux-gnueabihf" \
|
OPENSSL_INCLUDE_DIR="/usr/include/arm-linux-gnueabihf" \
|
||||||
OPENSSL_LIB_DIR="/usr/lib/arm-linux-gnueabihf"
|
OPENSSL_LIB_DIR="/usr/lib/arm-linux-gnueabihf"
|
||||||
|
|
||||||
|
|
||||||
# Creates a dummy project used to grab dependencies
|
# Creates a dummy project used to grab dependencies
|
||||||
RUN USER=root cargo new --bin /app
|
RUN USER=root cargo new --bin /app
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
@ -101,9 +96,14 @@ RUN touch src/main.rs
|
||||||
|
|
||||||
# Builds again, this time it'll just be
|
# Builds again, this time it'll just be
|
||||||
# your actual source files being built
|
# your actual source files being built
|
||||||
# hadolint ignore=DL3059
|
|
||||||
RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/.cargo/registry cargo build --features ${DB} --release --target=armv7-unknown-linux-gnueabihf
|
RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/.cargo/registry cargo build --features ${DB} --release --target=armv7-unknown-linux-gnueabihf
|
||||||
|
|
||||||
|
# Add the `cap_net_bind_service` capability to allow listening on
|
||||||
|
# privileged (< 1024) ports even when running as a non-root user.
|
||||||
|
# This is only done if building with BuildKit; with the legacy
|
||||||
|
# builder, the `COPY` instruction doesn't carry over capabilities.
|
||||||
|
RUN setcap cap_net_bind_service=+ep target/armv7-unknown-linux-gnueabihf/release/vaultwarden
|
||||||
|
|
||||||
######################## RUNTIME IMAGE ########################
|
######################## RUNTIME IMAGE ########################
|
||||||
# Create a new stage with a minimal image
|
# Create a new stage with a minimal image
|
||||||
# because we already have a binary built
|
# because we already have a binary built
|
||||||
|
@ -113,22 +113,20 @@ ENV ROCKET_PROFILE="release" \
|
||||||
ROCKET_ADDRESS=0.0.0.0 \
|
ROCKET_ADDRESS=0.0.0.0 \
|
||||||
ROCKET_PORT=80
|
ROCKET_PORT=80
|
||||||
|
|
||||||
# hadolint ignore=DL3059
|
|
||||||
RUN [ "cross-build-start" ]
|
RUN [ "cross-build-start" ]
|
||||||
|
|
||||||
# Create data folder and Install needed libraries
|
# Create data folder and Install needed libraries
|
||||||
RUN mkdir /data \
|
RUN mkdir /data \
|
||||||
&& apt-get update && apt-get install -y \
|
&& apt-get update && apt-get install -y \
|
||||||
--no-install-recommends \
|
--no-install-recommends \
|
||||||
openssl \
|
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
curl \
|
curl \
|
||||||
libmariadb-dev-compat \
|
libmariadb-dev-compat \
|
||||||
libpq5 \
|
libpq5 \
|
||||||
|
openssl \
|
||||||
&& apt-get clean \
|
&& apt-get clean \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
# hadolint ignore=DL3059
|
|
||||||
RUN [ "cross-build-end" ]
|
RUN [ "cross-build-end" ]
|
||||||
|
|
||||||
VOLUME /data
|
VOLUME /data
|
|
@ -29,8 +29,6 @@ FROM vaultwarden/web-vault@sha256:d5f71fb05c4b87935bf51d84140db0f8716cabfe2974fb
|
||||||
########################## BUILD IMAGE ##########################
|
########################## BUILD IMAGE ##########################
|
||||||
FROM blackdex/rust-musl:armv7-musleabihf-stable-1.66.1 as build
|
FROM blackdex/rust-musl:armv7-musleabihf-stable-1.66.1 as build
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Build time options to avoid dpkg warnings and help with reproducible builds.
|
# Build time options to avoid dpkg warnings and help with reproducible builds.
|
||||||
ENV DEBIAN_FRONTEND=noninteractive \
|
ENV DEBIAN_FRONTEND=noninteractive \
|
||||||
LANG=C.UTF-8 \
|
LANG=C.UTF-8 \
|
||||||
|
@ -39,7 +37,6 @@ ENV DEBIAN_FRONTEND=noninteractive \
|
||||||
CARGO_HOME="/root/.cargo" \
|
CARGO_HOME="/root/.cargo" \
|
||||||
USER="root"
|
USER="root"
|
||||||
|
|
||||||
|
|
||||||
# Create CARGO_HOME folder and don't download rust docs
|
# Create CARGO_HOME folder and don't download rust docs
|
||||||
RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/.cargo/registry mkdir -pv "${CARGO_HOME}" \
|
RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/.cargo/registry mkdir -pv "${CARGO_HOME}" \
|
||||||
&& rustup set profile minimal
|
&& rustup set profile minimal
|
||||||
|
@ -75,9 +72,14 @@ RUN touch src/main.rs
|
||||||
|
|
||||||
# Builds again, this time it'll just be
|
# Builds again, this time it'll just be
|
||||||
# your actual source files being built
|
# your actual source files being built
|
||||||
# hadolint ignore=DL3059
|
|
||||||
RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/.cargo/registry cargo build --features ${DB} --release --target=armv7-unknown-linux-musleabihf
|
RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/.cargo/registry cargo build --features ${DB} --release --target=armv7-unknown-linux-musleabihf
|
||||||
|
|
||||||
|
# Add the `cap_net_bind_service` capability to allow listening on
|
||||||
|
# privileged (< 1024) ports even when running as a non-root user.
|
||||||
|
# This is only done if building with BuildKit; with the legacy
|
||||||
|
# builder, the `COPY` instruction doesn't carry over capabilities.
|
||||||
|
RUN setcap cap_net_bind_service=+ep target/armv7-unknown-linux-musleabihf/release/vaultwarden
|
||||||
|
|
||||||
######################## RUNTIME IMAGE ########################
|
######################## RUNTIME IMAGE ########################
|
||||||
# Create a new stage with a minimal image
|
# Create a new stage with a minimal image
|
||||||
# because we already have a binary built
|
# because we already have a binary built
|
||||||
|
@ -89,18 +91,16 @@ ENV ROCKET_PROFILE="release" \
|
||||||
SSL_CERT_DIR=/etc/ssl/certs
|
SSL_CERT_DIR=/etc/ssl/certs
|
||||||
|
|
||||||
|
|
||||||
# hadolint ignore=DL3059
|
|
||||||
RUN [ "cross-build-start" ]
|
RUN [ "cross-build-start" ]
|
||||||
|
|
||||||
# Create data folder and Install needed libraries
|
# Create data folder and Install needed libraries
|
||||||
RUN mkdir /data \
|
RUN mkdir /data \
|
||||||
&& apk add --no-cache \
|
&& apk add --no-cache \
|
||||||
openssl \
|
ca-certificates \
|
||||||
tzdata \
|
|
||||||
curl \
|
curl \
|
||||||
ca-certificates
|
openssl \
|
||||||
|
tzdata
|
||||||
|
|
||||||
# hadolint ignore=DL3059
|
|
||||||
RUN [ "cross-build-end" ]
|
RUN [ "cross-build-end" ]
|
||||||
|
|
||||||
VOLUME /data
|
VOLUME /data
|
|
@ -23,7 +23,7 @@ LABELS=(
|
||||||
# https://github.com/opencontainers/image-spec/blob/master/annotations.md
|
# https://github.com/opencontainers/image-spec/blob/master/annotations.md
|
||||||
org.opencontainers.image.created="$(date --utc --iso-8601=seconds)"
|
org.opencontainers.image.created="$(date --utc --iso-8601=seconds)"
|
||||||
org.opencontainers.image.documentation="https://github.com/dani-garcia/vaultwarden/wiki"
|
org.opencontainers.image.documentation="https://github.com/dani-garcia/vaultwarden/wiki"
|
||||||
org.opencontainers.image.licenses="GPL-3.0-only"
|
org.opencontainers.image.licenses="AGPL-3.0-only"
|
||||||
org.opencontainers.image.revision="${SOURCE_COMMIT}"
|
org.opencontainers.image.revision="${SOURCE_COMMIT}"
|
||||||
org.opencontainers.image.source="${SOURCE_REPOSITORY_URL}"
|
org.opencontainers.image.source="${SOURCE_REPOSITORY_URL}"
|
||||||
org.opencontainers.image.url="https://hub.docker.com/r/${DOCKER_REPO#*/}"
|
org.opencontainers.image.url="https://hub.docker.com/r/${DOCKER_REPO#*/}"
|
||||||
|
@ -34,9 +34,9 @@ for label in "${LABELS[@]}"; do
|
||||||
LABEL_ARGS+=(--label "${label}")
|
LABEL_ARGS+=(--label "${label}")
|
||||||
done
|
done
|
||||||
|
|
||||||
# Check if DOCKER_BUILDKIT is set, if so, use the Dockerfile.buildx as template
|
# Check if DOCKER_BUILDKIT is set, if so, use the Dockerfile.buildkit as template
|
||||||
if [[ -n "${DOCKER_BUILDKIT}" ]]; then
|
if [[ -n "${DOCKER_BUILDKIT}" ]]; then
|
||||||
buildx_suffix=.buildx
|
buildkit_suffix=.buildkit
|
||||||
fi
|
fi
|
||||||
|
|
||||||
set -ex
|
set -ex
|
||||||
|
@ -45,6 +45,6 @@ for arch in "${arches[@]}"; do
|
||||||
docker build \
|
docker build \
|
||||||
"${LABEL_ARGS[@]}" \
|
"${LABEL_ARGS[@]}" \
|
||||||
-t "${DOCKER_REPO}:${DOCKER_TAG}-${arch}" \
|
-t "${DOCKER_REPO}:${DOCKER_TAG}-${arch}" \
|
||||||
-f docker/${arch}/Dockerfile${buildx_suffix}${distro_suffix} \
|
-f docker/${arch}/Dockerfile${buildkit_suffix}${distro_suffix} \
|
||||||
.
|
.
|
||||||
done
|
done
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
ALTER TABLE users_organizations
|
||||||
|
ADD COLUMN reset_password_key TEXT;
|
|
@ -0,0 +1,2 @@
|
||||||
|
ALTER TABLE users_organizations
|
||||||
|
ADD COLUMN reset_password_key TEXT;
|
|
@ -0,0 +1,2 @@
|
||||||
|
ALTER TABLE users_organizations
|
||||||
|
ADD COLUMN reset_password_key TEXT;
|
|
@ -123,7 +123,9 @@ async fn post_emergency_access(
|
||||||
|
|
||||||
emergency_access.atype = new_type;
|
emergency_access.atype = new_type;
|
||||||
emergency_access.wait_time_days = data.WaitTimeDays;
|
emergency_access.wait_time_days = data.WaitTimeDays;
|
||||||
|
if data.KeyEncrypted.is_some() {
|
||||||
emergency_access.key_encrypted = data.KeyEncrypted;
|
emergency_access.key_encrypted = data.KeyEncrypted;
|
||||||
|
}
|
||||||
|
|
||||||
emergency_access.save(&mut conn).await?;
|
emergency_access.save(&mut conn).await?;
|
||||||
Ok(Json(emergency_access.to_json()))
|
Ok(Json(emergency_access.to_json()))
|
||||||
|
|
|
@ -62,6 +62,7 @@ pub fn routes() -> Vec<Route> {
|
||||||
get_plans_tax_rates,
|
get_plans_tax_rates,
|
||||||
import,
|
import,
|
||||||
post_org_keys,
|
post_org_keys,
|
||||||
|
get_organization_keys,
|
||||||
bulk_public_keys,
|
bulk_public_keys,
|
||||||
deactivate_organization_user,
|
deactivate_organization_user,
|
||||||
bulk_deactivate_organization_user,
|
bulk_deactivate_organization_user,
|
||||||
|
@ -86,6 +87,9 @@ pub fn routes() -> Vec<Route> {
|
||||||
put_user_groups,
|
put_user_groups,
|
||||||
delete_group_user,
|
delete_group_user,
|
||||||
post_delete_group_user,
|
post_delete_group_user,
|
||||||
|
put_reset_password_enrollment,
|
||||||
|
get_reset_password_details,
|
||||||
|
put_reset_password,
|
||||||
get_org_export
|
get_org_export
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -882,6 +886,7 @@ async fn _reinvite_user(org_id: &str, user_org: &str, invited_by_email: &str, co
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
struct AcceptData {
|
struct AcceptData {
|
||||||
Token: String,
|
Token: String,
|
||||||
|
ResetPasswordKey: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/organizations/<org_id>/users/<_org_user_id>/accept", data = "<data>")]
|
#[post("/organizations/<org_id>/users/<_org_user_id>/accept", data = "<data>")]
|
||||||
|
@ -909,6 +914,11 @@ async fn accept_invite(
|
||||||
err!("User already accepted the invitation")
|
err!("User already accepted the invitation")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let master_password_required = OrgPolicy::org_is_reset_password_auto_enroll(org, &mut conn).await;
|
||||||
|
if data.ResetPasswordKey.is_none() && master_password_required {
|
||||||
|
err!("Reset password key is required, but not provided.");
|
||||||
|
}
|
||||||
|
|
||||||
// This check is also done at accept_invite(), _confirm_invite, _activate_user(), edit_user(), admin::update_user_org_type
|
// This check is also done at accept_invite(), _confirm_invite, _activate_user(), edit_user(), admin::update_user_org_type
|
||||||
// It returns different error messages per function.
|
// It returns different error messages per function.
|
||||||
if user_org.atype < UserOrgType::Admin {
|
if user_org.atype < UserOrgType::Admin {
|
||||||
|
@ -924,6 +934,11 @@ async fn accept_invite(
|
||||||
}
|
}
|
||||||
|
|
||||||
user_org.status = UserOrgStatus::Accepted as i32;
|
user_org.status = UserOrgStatus::Accepted as i32;
|
||||||
|
|
||||||
|
if master_password_required {
|
||||||
|
user_org.reset_password_key = data.ResetPasswordKey;
|
||||||
|
}
|
||||||
|
|
||||||
user_org.save(&mut conn).await?;
|
user_org.save(&mut conn).await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2460,6 +2475,204 @@ async fn delete_group_user(
|
||||||
GroupUser::delete_by_group_id_and_user_id(&group_id, &org_user_id, &mut conn).await
|
GroupUser::delete_by_group_id_and_user_id(&group_id, &org_user_id, &mut conn).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
struct OrganizationUserResetPasswordEnrollmentRequest {
|
||||||
|
ResetPasswordKey: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
struct OrganizationUserResetPasswordRequest {
|
||||||
|
NewMasterPasswordHash: String,
|
||||||
|
Key: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/organizations/<org_id>/keys")]
|
||||||
|
async fn get_organization_keys(org_id: String, mut conn: DbConn) -> JsonResult {
|
||||||
|
let org = match Organization::find_by_uuid(&org_id, &mut conn).await {
|
||||||
|
Some(organization) => organization,
|
||||||
|
None => err!("Organization not found"),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Json(json!({
|
||||||
|
"Object": "organizationKeys",
|
||||||
|
"PublicKey": org.public_key,
|
||||||
|
"PrivateKey": org.private_key,
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[put("/organizations/<org_id>/users/<org_user_id>/reset-password", data = "<data>")]
|
||||||
|
async fn put_reset_password(
|
||||||
|
org_id: String,
|
||||||
|
org_user_id: String,
|
||||||
|
headers: AdminHeaders,
|
||||||
|
data: JsonUpcase<OrganizationUserResetPasswordRequest>,
|
||||||
|
mut conn: DbConn,
|
||||||
|
ip: ClientIp,
|
||||||
|
nt: Notify<'_>,
|
||||||
|
) -> EmptyResult {
|
||||||
|
let org = match Organization::find_by_uuid(&org_id, &mut conn).await {
|
||||||
|
Some(org) => org,
|
||||||
|
None => err!("Required organization not found"),
|
||||||
|
};
|
||||||
|
|
||||||
|
let org_user = match UserOrganization::find_by_uuid_and_org(&org_user_id, &org.uuid, &mut conn).await {
|
||||||
|
Some(user) => user,
|
||||||
|
None => err!("User to reset isn't member of required organization"),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut user = match User::find_by_uuid(&org_user.user_uuid, &mut conn).await {
|
||||||
|
Some(user) => user,
|
||||||
|
None => err!("User not found"),
|
||||||
|
};
|
||||||
|
|
||||||
|
check_reset_password_applicable_and_permissions(&org_id, &org_user_id, &headers, &mut conn).await?;
|
||||||
|
|
||||||
|
if org_user.reset_password_key.is_none() {
|
||||||
|
err!("Password reset not or not correctly enrolled");
|
||||||
|
}
|
||||||
|
if org_user.status != (UserOrgStatus::Confirmed as i32) {
|
||||||
|
err!("Organization user must be confirmed for password reset functionality");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sending email before resetting password to ensure working email configuration and the resulting
|
||||||
|
// user notification. Also this might add some protection against security flaws and misuse
|
||||||
|
if let Err(e) = mail::send_admin_reset_password(&user.email, &user.name, &org.name).await {
|
||||||
|
error!("Error sending user reset password email: {:#?}", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
let reset_request = data.into_inner().data;
|
||||||
|
|
||||||
|
user.set_password(reset_request.NewMasterPasswordHash.as_str(), Some(reset_request.Key), true, None);
|
||||||
|
user.save(&mut conn).await?;
|
||||||
|
|
||||||
|
nt.send_logout(&user, None).await;
|
||||||
|
|
||||||
|
log_event(
|
||||||
|
EventType::OrganizationUserAdminResetPassword as i32,
|
||||||
|
&org_user_id,
|
||||||
|
org.uuid.clone(),
|
||||||
|
headers.user.uuid.clone(),
|
||||||
|
headers.device.atype,
|
||||||
|
&ip.ip,
|
||||||
|
&mut conn,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/organizations/<org_id>/users/<org_user_id>/reset-password-details")]
|
||||||
|
async fn get_reset_password_details(
|
||||||
|
org_id: String,
|
||||||
|
org_user_id: String,
|
||||||
|
headers: AdminHeaders,
|
||||||
|
mut conn: DbConn,
|
||||||
|
) -> JsonResult {
|
||||||
|
let org = match Organization::find_by_uuid(&org_id, &mut conn).await {
|
||||||
|
Some(org) => org,
|
||||||
|
None => err!("Required organization not found"),
|
||||||
|
};
|
||||||
|
|
||||||
|
let org_user = match UserOrganization::find_by_uuid_and_org(&org_user_id, &org_id, &mut conn).await {
|
||||||
|
Some(user) => user,
|
||||||
|
None => err!("User to reset isn't member of required organization"),
|
||||||
|
};
|
||||||
|
|
||||||
|
let user = match User::find_by_uuid(&org_user.user_uuid, &mut conn).await {
|
||||||
|
Some(user) => user,
|
||||||
|
None => err!("User not found"),
|
||||||
|
};
|
||||||
|
|
||||||
|
check_reset_password_applicable_and_permissions(&org_id, &org_user_id, &headers, &mut conn).await?;
|
||||||
|
|
||||||
|
Ok(Json(json!({
|
||||||
|
"Object": "organizationUserResetPasswordDetails",
|
||||||
|
"Kdf":user.client_kdf_type,
|
||||||
|
"KdfIterations":user.client_kdf_iter,
|
||||||
|
"ResetPasswordKey":org_user.reset_password_key,
|
||||||
|
"EncryptedPrivateKey":org.private_key ,
|
||||||
|
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn check_reset_password_applicable_and_permissions(
|
||||||
|
org_id: &str,
|
||||||
|
org_user_id: &str,
|
||||||
|
headers: &AdminHeaders,
|
||||||
|
conn: &mut DbConn,
|
||||||
|
) -> EmptyResult {
|
||||||
|
check_reset_password_applicable(org_id, conn).await?;
|
||||||
|
|
||||||
|
let target_user = match UserOrganization::find_by_uuid_and_org(org_user_id, org_id, conn).await {
|
||||||
|
Some(user) => user,
|
||||||
|
None => err!("Reset target user not found"),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Resetting user must be higher/equal to user to reset
|
||||||
|
match headers.org_user_type {
|
||||||
|
UserOrgType::Owner => Ok(()),
|
||||||
|
UserOrgType::Admin if target_user.atype <= UserOrgType::Admin => Ok(()),
|
||||||
|
_ => err!("No permission to reset this user's password"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn check_reset_password_applicable(org_id: &str, conn: &mut DbConn) -> EmptyResult {
|
||||||
|
if !CONFIG.mail_enabled() {
|
||||||
|
err!("Password reset is not supported on an email-disabled instance.");
|
||||||
|
}
|
||||||
|
|
||||||
|
let policy = match OrgPolicy::find_by_org_and_type(org_id, OrgPolicyType::ResetPassword, conn).await {
|
||||||
|
Some(p) => p,
|
||||||
|
None => err!("Policy not found"),
|
||||||
|
};
|
||||||
|
|
||||||
|
if !policy.enabled {
|
||||||
|
err!("Reset password policy not enabled");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[put("/organizations/<org_id>/users/<org_user_id>/reset-password-enrollment", data = "<data>")]
|
||||||
|
async fn put_reset_password_enrollment(
|
||||||
|
org_id: String,
|
||||||
|
org_user_id: String,
|
||||||
|
headers: Headers,
|
||||||
|
data: JsonUpcase<OrganizationUserResetPasswordEnrollmentRequest>,
|
||||||
|
mut conn: DbConn,
|
||||||
|
ip: ClientIp,
|
||||||
|
) -> EmptyResult {
|
||||||
|
let mut org_user = match UserOrganization::find_by_user_and_org(&headers.user.uuid, &org_id, &mut conn).await {
|
||||||
|
Some(u) => u,
|
||||||
|
None => err!("User to enroll isn't member of required organization"),
|
||||||
|
};
|
||||||
|
|
||||||
|
check_reset_password_applicable(&org_id, &mut conn).await?;
|
||||||
|
|
||||||
|
let reset_request = data.into_inner().data;
|
||||||
|
|
||||||
|
if reset_request.ResetPasswordKey.is_none()
|
||||||
|
&& OrgPolicy::org_is_reset_password_auto_enroll(&org_id, &mut conn).await
|
||||||
|
{
|
||||||
|
err!("Reset password can't be withdrawed due to an enterprise policy");
|
||||||
|
}
|
||||||
|
|
||||||
|
org_user.reset_password_key = reset_request.ResetPasswordKey;
|
||||||
|
org_user.save(&mut conn).await?;
|
||||||
|
|
||||||
|
let log_id = if org_user.reset_password_key.is_some() {
|
||||||
|
EventType::OrganizationUserResetPasswordEnroll as i32
|
||||||
|
} else {
|
||||||
|
EventType::OrganizationUserResetPasswordWithdraw as i32
|
||||||
|
};
|
||||||
|
|
||||||
|
log_event(log_id, &org_user_id, org_id, headers.user.uuid.clone(), headers.device.atype, &ip.ip, &mut conn).await;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
// This is a new function active since the v2022.9.x clients.
|
// This is a new function active since the v2022.9.x clients.
|
||||||
// It combines the previous two calls done before.
|
// It combines the previous two calls done before.
|
||||||
// We call those two functions here and combine them our selfs.
|
// We call those two functions here and combine them our selfs.
|
||||||
|
|
|
@ -79,7 +79,7 @@ async fn icon_redirect(domain: &str, template: &str) -> Option<Redirect> {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
if is_domain_blacklisted(domain).await {
|
if check_domain_blacklist_reason(domain).await.is_some() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -258,9 +258,15 @@ mod tests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
enum DomainBlacklistReason {
|
||||||
|
Regex,
|
||||||
|
IP,
|
||||||
|
}
|
||||||
|
|
||||||
use cached::proc_macro::cached;
|
use cached::proc_macro::cached;
|
||||||
#[cached(key = "String", convert = r#"{ domain.to_string() }"#, size = 16, time = 60)]
|
#[cached(key = "String", convert = r#"{ domain.to_string() }"#, size = 16, time = 60)]
|
||||||
async fn is_domain_blacklisted(domain: &str) -> bool {
|
async fn check_domain_blacklist_reason(domain: &str) -> Option<DomainBlacklistReason> {
|
||||||
// First check the blacklist regex if there is a match.
|
// First check the blacklist regex if there is a match.
|
||||||
// This prevents the blocked domain(s) from being leaked via a DNS lookup.
|
// This prevents the blocked domain(s) from being leaked via a DNS lookup.
|
||||||
if let Some(blacklist) = CONFIG.icon_blacklist_regex() {
|
if let Some(blacklist) = CONFIG.icon_blacklist_regex() {
|
||||||
|
@ -284,7 +290,7 @@ async fn is_domain_blacklisted(domain: &str) -> bool {
|
||||||
|
|
||||||
if is_match {
|
if is_match {
|
||||||
debug!("Blacklisted domain: {} matched ICON_BLACKLIST_REGEX", domain);
|
debug!("Blacklisted domain: {} matched ICON_BLACKLIST_REGEX", domain);
|
||||||
return true;
|
return Some(DomainBlacklistReason::Regex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -293,13 +299,13 @@ async fn is_domain_blacklisted(domain: &str) -> bool {
|
||||||
for addr in s {
|
for addr in s {
|
||||||
if !is_global(addr.ip()) {
|
if !is_global(addr.ip()) {
|
||||||
debug!("IP {} for domain '{}' is not a global IP!", addr.ip(), domain);
|
debug!("IP {} for domain '{}' is not a global IP!", addr.ip(), domain);
|
||||||
return true;
|
return Some(DomainBlacklistReason::IP);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
false
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_icon(domain: &str) -> Option<(Vec<u8>, String)> {
|
async fn get_icon(domain: &str) -> Option<(Vec<u8>, String)> {
|
||||||
|
@ -564,8 +570,10 @@ async fn get_page(url: &str) -> Result<Response, Error> {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_page_with_referer(url: &str, referer: &str) -> Result<Response, Error> {
|
async fn get_page_with_referer(url: &str, referer: &str) -> Result<Response, Error> {
|
||||||
if is_domain_blacklisted(url::Url::parse(url).unwrap().host_str().unwrap_or_default()).await {
|
match check_domain_blacklist_reason(url::Url::parse(url).unwrap().host_str().unwrap_or_default()).await {
|
||||||
warn!("Favicon '{}' resolves to a blacklisted domain or IP!", url);
|
Some(DomainBlacklistReason::Regex) => warn!("Favicon '{}' is from a blacklisted domain!", url),
|
||||||
|
Some(DomainBlacklistReason::IP) => warn!("Favicon '{}' is hosted on a non-global IP!", url),
|
||||||
|
None => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut client = CLIENT.get(url);
|
let mut client = CLIENT.get(url);
|
||||||
|
@ -659,8 +667,10 @@ fn parse_sizes(sizes: &str) -> (u16, u16) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn download_icon(domain: &str) -> Result<(Bytes, Option<&str>), Error> {
|
async fn download_icon(domain: &str) -> Result<(Bytes, Option<&str>), Error> {
|
||||||
if is_domain_blacklisted(domain).await {
|
match check_domain_blacklist_reason(domain).await {
|
||||||
err_silent!("Domain is blacklisted", domain)
|
Some(DomainBlacklistReason::Regex) => err_silent!("Domain is blacklisted", domain),
|
||||||
|
Some(DomainBlacklistReason::IP) => err_silent!("Host resolves to a non-global IP", domain),
|
||||||
|
None => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
let icon_result = get_icon_url(domain).await?;
|
let icon_result = get_icon_url(domain).await?;
|
||||||
|
|
|
@ -118,8 +118,8 @@ pub fn static_files(filename: String) -> Result<(ContentType, &'static [u8]), Er
|
||||||
"jdenticon.js" => Ok((ContentType::JavaScript, include_bytes!("../static/scripts/jdenticon.js"))),
|
"jdenticon.js" => Ok((ContentType::JavaScript, include_bytes!("../static/scripts/jdenticon.js"))),
|
||||||
"datatables.js" => Ok((ContentType::JavaScript, include_bytes!("../static/scripts/datatables.js"))),
|
"datatables.js" => Ok((ContentType::JavaScript, include_bytes!("../static/scripts/datatables.js"))),
|
||||||
"datatables.css" => Ok((ContentType::CSS, include_bytes!("../static/scripts/datatables.css"))),
|
"datatables.css" => Ok((ContentType::CSS, include_bytes!("../static/scripts/datatables.css"))),
|
||||||
"jquery-3.6.2.slim.js" => {
|
"jquery-3.6.3.slim.js" => {
|
||||||
Ok((ContentType::JavaScript, include_bytes!("../static/scripts/jquery-3.6.2.slim.js")))
|
Ok((ContentType::JavaScript, include_bytes!("../static/scripts/jquery-3.6.3.slim.js")))
|
||||||
}
|
}
|
||||||
_ => err!(format!("Static file not found: {filename}")),
|
_ => err!(format!("Static file not found: {filename}")),
|
||||||
}
|
}
|
||||||
|
|
|
@ -141,6 +141,8 @@ macro_rules! make_config {
|
||||||
)+)+
|
)+)+
|
||||||
config.domain_set = _domain_set;
|
config.domain_set = _domain_set;
|
||||||
|
|
||||||
|
config.domain = config.domain.trim_end_matches('/').to_string();
|
||||||
|
|
||||||
config.signups_domains_whitelist = config.signups_domains_whitelist.trim().to_lowercase();
|
config.signups_domains_whitelist = config.signups_domains_whitelist.trim().to_lowercase();
|
||||||
config.org_creation_users = config.org_creation_users.trim().to_lowercase();
|
config.org_creation_users = config.org_creation_users.trim().to_lowercase();
|
||||||
|
|
||||||
|
@ -1136,6 +1138,7 @@ where
|
||||||
reg!("email/email_footer");
|
reg!("email/email_footer");
|
||||||
reg!("email/email_footer_text");
|
reg!("email/email_footer_text");
|
||||||
|
|
||||||
|
reg!("email/admin_reset_password", ".html");
|
||||||
reg!("email/change_email", ".html");
|
reg!("email/change_email", ".html");
|
||||||
reg!("email/delete_account", ".html");
|
reg!("email/delete_account", ".html");
|
||||||
reg!("email/emergency_access_invite_accepted", ".html");
|
reg!("email/emergency_access_invite_accepted", ".html");
|
||||||
|
|
|
@ -287,40 +287,90 @@ impl Collection {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn is_writable_by_user(&self, user_uuid: &str, conn: &mut DbConn) -> bool {
|
pub async fn is_writable_by_user(&self, user_uuid: &str, conn: &mut DbConn) -> bool {
|
||||||
match UserOrganization::find_by_user_and_org(user_uuid, &self.org_uuid, conn).await {
|
let user_uuid = user_uuid.to_string();
|
||||||
None => false, // Not in Org
|
|
||||||
Some(user_org) => {
|
|
||||||
if user_org.has_full_access() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
db_run! { conn: {
|
db_run! { conn: {
|
||||||
users_collections::table
|
collections::table
|
||||||
.filter(users_collections::collection_uuid.eq(&self.uuid))
|
.left_join(users_collections::table.on(
|
||||||
.filter(users_collections::user_uuid.eq(user_uuid))
|
users_collections::collection_uuid.eq(collections::uuid).and(
|
||||||
.filter(users_collections::read_only.eq(false))
|
users_collections::user_uuid.eq(user_uuid.clone())
|
||||||
|
)
|
||||||
|
))
|
||||||
|
.left_join(users_organizations::table.on(
|
||||||
|
collections::org_uuid.eq(users_organizations::org_uuid).and(
|
||||||
|
users_organizations::user_uuid.eq(user_uuid)
|
||||||
|
)
|
||||||
|
))
|
||||||
|
.left_join(groups_users::table.on(
|
||||||
|
groups_users::users_organizations_uuid.eq(users_organizations::uuid)
|
||||||
|
))
|
||||||
|
.left_join(groups::table.on(
|
||||||
|
groups::uuid.eq(groups_users::groups_uuid)
|
||||||
|
))
|
||||||
|
.left_join(collections_groups::table.on(
|
||||||
|
collections_groups::groups_uuid.eq(groups_users::groups_uuid).and(
|
||||||
|
collections_groups::collections_uuid.eq(collections::uuid)
|
||||||
|
)
|
||||||
|
))
|
||||||
|
.filter(collections::uuid.eq(&self.uuid))
|
||||||
|
.filter(
|
||||||
|
users_collections::collection_uuid.eq(&self.uuid).and(users_collections::read_only.eq(false)).or(// Directly accessed collection
|
||||||
|
users_organizations::access_all.eq(true).or( // access_all in Organization
|
||||||
|
users_organizations::atype.le(UserOrgType::Admin as i32) // Org admin or owner
|
||||||
|
)).or(
|
||||||
|
groups::access_all.eq(true) // access_all in groups
|
||||||
|
).or( // access via groups
|
||||||
|
groups_users::users_organizations_uuid.eq(users_organizations::uuid).and(
|
||||||
|
collections_groups::collections_uuid.is_not_null().and(
|
||||||
|
collections_groups::read_only.eq(false))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
.count()
|
.count()
|
||||||
.first::<i64>(conn)
|
.first::<i64>(conn)
|
||||||
.ok()
|
.ok()
|
||||||
.unwrap_or(0) != 0
|
.unwrap_or(0) != 0
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn hide_passwords_for_user(&self, user_uuid: &str, conn: &mut DbConn) -> bool {
|
pub async fn hide_passwords_for_user(&self, user_uuid: &str, conn: &mut DbConn) -> bool {
|
||||||
match UserOrganization::find_by_user_and_org(user_uuid, &self.org_uuid, conn).await {
|
let user_uuid = user_uuid.to_string();
|
||||||
None => true, // Not in Org
|
|
||||||
Some(user_org) => {
|
|
||||||
if user_org.has_full_access() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
db_run! { conn: {
|
db_run! { conn: {
|
||||||
users_collections::table
|
collections::table
|
||||||
.filter(users_collections::collection_uuid.eq(&self.uuid))
|
.left_join(users_collections::table.on(
|
||||||
.filter(users_collections::user_uuid.eq(user_uuid))
|
users_collections::collection_uuid.eq(collections::uuid).and(
|
||||||
.filter(users_collections::hide_passwords.eq(true))
|
users_collections::user_uuid.eq(user_uuid.clone())
|
||||||
|
)
|
||||||
|
))
|
||||||
|
.left_join(users_organizations::table.on(
|
||||||
|
collections::org_uuid.eq(users_organizations::org_uuid).and(
|
||||||
|
users_organizations::user_uuid.eq(user_uuid)
|
||||||
|
)
|
||||||
|
))
|
||||||
|
.left_join(groups_users::table.on(
|
||||||
|
groups_users::users_organizations_uuid.eq(users_organizations::uuid)
|
||||||
|
))
|
||||||
|
.left_join(groups::table.on(
|
||||||
|
groups::uuid.eq(groups_users::groups_uuid)
|
||||||
|
))
|
||||||
|
.left_join(collections_groups::table.on(
|
||||||
|
collections_groups::groups_uuid.eq(groups_users::groups_uuid).and(
|
||||||
|
collections_groups::collections_uuid.eq(collections::uuid)
|
||||||
|
)
|
||||||
|
))
|
||||||
|
.filter(collections::uuid.eq(&self.uuid))
|
||||||
|
.filter(
|
||||||
|
users_collections::collection_uuid.eq(&self.uuid).and(users_collections::hide_passwords.eq(true)).or(// Directly accessed collection
|
||||||
|
users_organizations::access_all.eq(true).or( // access_all in Organization
|
||||||
|
users_organizations::atype.le(UserOrgType::Admin as i32) // Org admin or owner
|
||||||
|
)).or(
|
||||||
|
groups::access_all.eq(true) // access_all in groups
|
||||||
|
).or( // access via groups
|
||||||
|
groups_users::users_organizations_uuid.eq(users_organizations::uuid).and(
|
||||||
|
collections_groups::collections_uuid.is_not_null().and(
|
||||||
|
collections_groups::hide_passwords.eq(true))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
.count()
|
.count()
|
||||||
.first::<i64>(conn)
|
.first::<i64>(conn)
|
||||||
.ok()
|
.ok()
|
||||||
|
@ -328,8 +378,6 @@ impl Collection {
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Database methods
|
/// Database methods
|
||||||
impl CollectionUser {
|
impl CollectionUser {
|
||||||
|
|
|
@ -87,9 +87,9 @@ pub enum EventType {
|
||||||
OrganizationUserRemoved = 1503,
|
OrganizationUserRemoved = 1503,
|
||||||
OrganizationUserUpdatedGroups = 1504,
|
OrganizationUserUpdatedGroups = 1504,
|
||||||
// OrganizationUserUnlinkedSso = 1505, // Not supported
|
// OrganizationUserUnlinkedSso = 1505, // Not supported
|
||||||
// OrganizationUserResetPasswordEnroll = 1506, // Not supported
|
OrganizationUserResetPasswordEnroll = 1506,
|
||||||
// OrganizationUserResetPasswordWithdraw = 1507, // Not supported
|
OrganizationUserResetPasswordWithdraw = 1507,
|
||||||
// OrganizationUserAdminResetPassword = 1508, // Not supported
|
OrganizationUserAdminResetPassword = 1508,
|
||||||
// OrganizationUserResetSsoLink = 1509, // Not supported
|
// OrganizationUserResetSsoLink = 1509, // Not supported
|
||||||
// OrganizationUserFirstSsoLogin = 1510, // Not supported
|
// OrganizationUserFirstSsoLogin = 1510, // Not supported
|
||||||
OrganizationUserRevoked = 1511,
|
OrganizationUserRevoked = 1511,
|
||||||
|
|
|
@ -32,7 +32,7 @@ pub enum OrgPolicyType {
|
||||||
PersonalOwnership = 5,
|
PersonalOwnership = 5,
|
||||||
DisableSend = 6,
|
DisableSend = 6,
|
||||||
SendOptions = 7,
|
SendOptions = 7,
|
||||||
// ResetPassword = 8, // Not supported
|
ResetPassword = 8,
|
||||||
// MaximumVaultTimeout = 9, // Not supported (Not AGPLv3 Licensed)
|
// MaximumVaultTimeout = 9, // Not supported (Not AGPLv3 Licensed)
|
||||||
// DisablePersonalVaultExport = 10, // Not supported (Not AGPLv3 Licensed)
|
// DisablePersonalVaultExport = 10, // Not supported (Not AGPLv3 Licensed)
|
||||||
}
|
}
|
||||||
|
@ -44,6 +44,13 @@ pub struct SendOptionsPolicyData {
|
||||||
pub DisableHideEmail: bool,
|
pub DisableHideEmail: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://github.com/bitwarden/server/blob/5cbdee137921a19b1f722920f0fa3cd45af2ef0f/src/Core/Models/Data/Organizations/Policies/ResetPasswordDataModel.cs
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
pub struct ResetPasswordDataModel {
|
||||||
|
pub AutoEnrollEnabled: bool,
|
||||||
|
}
|
||||||
|
|
||||||
pub type OrgPolicyResult = Result<(), OrgPolicyErr>;
|
pub type OrgPolicyResult = Result<(), OrgPolicyErr>;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -298,6 +305,20 @@ impl OrgPolicy {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn org_is_reset_password_auto_enroll(org_uuid: &str, conn: &mut DbConn) -> bool {
|
||||||
|
match OrgPolicy::find_by_org_and_type(org_uuid, OrgPolicyType::ResetPassword, conn).await {
|
||||||
|
Some(policy) => match serde_json::from_str::<UpCase<ResetPasswordDataModel>>(&policy.data) {
|
||||||
|
Ok(opts) => {
|
||||||
|
return opts.data.AutoEnrollEnabled;
|
||||||
|
}
|
||||||
|
_ => error!("Failed to deserialize ResetPasswordDataModel: {}", policy.data),
|
||||||
|
},
|
||||||
|
None => return false,
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns true if the user belongs to an org that has enabled the `DisableHideEmail`
|
/// Returns true if the user belongs to an org that has enabled the `DisableHideEmail`
|
||||||
/// option of the `Send Options` policy, and the user is not an owner or admin of that org.
|
/// option of the `Send Options` policy, and the user is not an owner or admin of that org.
|
||||||
pub async fn is_hide_email_disabled(user_uuid: &str, conn: &mut DbConn) -> bool {
|
pub async fn is_hide_email_disabled(user_uuid: &str, conn: &mut DbConn) -> bool {
|
||||||
|
|
|
@ -29,6 +29,7 @@ db_object! {
|
||||||
pub akey: String,
|
pub akey: String,
|
||||||
pub status: i32,
|
pub status: i32,
|
||||||
pub atype: i32,
|
pub atype: i32,
|
||||||
|
pub reset_password_key: Option<String>,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -158,7 +159,7 @@ impl Organization {
|
||||||
"SelfHost": true,
|
"SelfHost": true,
|
||||||
"UseApi": false, // Not supported
|
"UseApi": false, // Not supported
|
||||||
"HasPublicAndPrivateKeys": self.private_key.is_some() && self.public_key.is_some(),
|
"HasPublicAndPrivateKeys": self.private_key.is_some() && self.public_key.is_some(),
|
||||||
"UseResetPassword": false, // Not supported
|
"UseResetPassword": CONFIG.mail_enabled(),
|
||||||
|
|
||||||
"BusinessName": null,
|
"BusinessName": null,
|
||||||
"BusinessAddress1": null,
|
"BusinessAddress1": null,
|
||||||
|
@ -194,6 +195,7 @@ impl UserOrganization {
|
||||||
akey: String::new(),
|
akey: String::new(),
|
||||||
status: UserOrgStatus::Accepted as i32,
|
status: UserOrgStatus::Accepted as i32,
|
||||||
atype: UserOrgType::User as i32,
|
atype: UserOrgType::User as i32,
|
||||||
|
reset_password_key: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -311,7 +313,8 @@ impl UserOrganization {
|
||||||
"UseApi": false, // Not supported
|
"UseApi": false, // Not supported
|
||||||
"SelfHost": true,
|
"SelfHost": true,
|
||||||
"HasPublicAndPrivateKeys": org.private_key.is_some() && org.public_key.is_some(),
|
"HasPublicAndPrivateKeys": org.private_key.is_some() && org.public_key.is_some(),
|
||||||
"ResetPasswordEnrolled": false, // Not supported
|
"ResetPasswordEnrolled": self.reset_password_key.is_some(),
|
||||||
|
"UseResetPassword": CONFIG.mail_enabled(),
|
||||||
"SsoBound": false, // Not supported
|
"SsoBound": false, // Not supported
|
||||||
"UseSso": false, // Not supported
|
"UseSso": false, // Not supported
|
||||||
"ProviderId": null,
|
"ProviderId": null,
|
||||||
|
@ -377,6 +380,7 @@ impl UserOrganization {
|
||||||
"Type": self.atype,
|
"Type": self.atype,
|
||||||
"AccessAll": self.access_all,
|
"AccessAll": self.access_all,
|
||||||
"TwoFactorEnabled": twofactor_enabled,
|
"TwoFactorEnabled": twofactor_enabled,
|
||||||
|
"ResetPasswordEnrolled":self.reset_password_key.is_some(),
|
||||||
|
|
||||||
"Object": "organizationUserUserDetails",
|
"Object": "organizationUserUserDetails",
|
||||||
})
|
})
|
||||||
|
|
|
@ -224,6 +224,7 @@ table! {
|
||||||
akey -> Text,
|
akey -> Text,
|
||||||
status -> Integer,
|
status -> Integer,
|
||||||
atype -> Integer,
|
atype -> Integer,
|
||||||
|
reset_password_key -> Nullable<Text>,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -224,6 +224,7 @@ table! {
|
||||||
akey -> Text,
|
akey -> Text,
|
||||||
status -> Integer,
|
status -> Integer,
|
||||||
atype -> Integer,
|
atype -> Integer,
|
||||||
|
reset_password_key -> Nullable<Text>,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -224,6 +224,7 @@ table! {
|
||||||
akey -> Text,
|
akey -> Text,
|
||||||
status -> Integer,
|
status -> Integer,
|
||||||
atype -> Integer,
|
atype -> Integer,
|
||||||
|
reset_password_key -> Nullable<Text>,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
13
src/mail.rs
13
src/mail.rs
|
@ -496,6 +496,19 @@ pub async fn send_test(address: &str) -> EmptyResult {
|
||||||
send_email(address, &subject, body_html, body_text).await
|
send_email(address, &subject, body_html, body_text).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn send_admin_reset_password(address: &str, user_name: &str, org_name: &str) -> EmptyResult {
|
||||||
|
let (subject, body_html, body_text) = get_text(
|
||||||
|
"email/admin_reset_password",
|
||||||
|
json!({
|
||||||
|
"url": CONFIG.domain(),
|
||||||
|
"img_src": CONFIG._smtp_img_src(),
|
||||||
|
"user_name": user_name,
|
||||||
|
"org_name": org_name,
|
||||||
|
}),
|
||||||
|
)?;
|
||||||
|
send_email(address, &subject, body_html, body_text).await
|
||||||
|
}
|
||||||
|
|
||||||
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 = &CONFIG.smtp_from();
|
||||||
|
|
||||||
|
|
21
src/static/scripts/admin.css
gevendort
21
src/static/scripts/admin.css
gevendort
|
@ -18,24 +18,31 @@ img {
|
||||||
border: var(--bs-alert-border);
|
border: var(--bs-alert-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#users-table .vw-account-details {
|
||||||
|
min-width: 250px;
|
||||||
|
}
|
||||||
#users-table .vw-created-at, #users-table .vw-last-active {
|
#users-table .vw-created-at, #users-table .vw-last-active {
|
||||||
width: 85px;
|
min-width: 85px;
|
||||||
min-width: 70px;
|
max-width: 85px;
|
||||||
}
|
}
|
||||||
#users-table .vw-items {
|
#users-table .vw-items, #orgs-table .vw-items, #orgs-table .vw-users {
|
||||||
width: 35px;
|
|
||||||
min-width: 35px;
|
min-width: 35px;
|
||||||
|
max-width: 40px;
|
||||||
}
|
}
|
||||||
#users-table .vw-organizations {
|
#users-table .vw-attachments, #orgs-table .vw-attachments {
|
||||||
min-width: 120px;
|
min-width: 100px;
|
||||||
|
max-width: 130px;
|
||||||
}
|
}
|
||||||
#users-table .vw-actions, #orgs-table .vw-actions {
|
#users-table .vw-actions, #orgs-table .vw-actions {
|
||||||
width: 130px;
|
|
||||||
min-width: 130px;
|
min-width: 130px;
|
||||||
|
max-width: 130px;
|
||||||
}
|
}
|
||||||
#users-table .vw-org-cell {
|
#users-table .vw-org-cell {
|
||||||
max-height: 120px;
|
max-height: 120px;
|
||||||
}
|
}
|
||||||
|
#orgs-table .vw-org-details {
|
||||||
|
min-width: 285px;
|
||||||
|
}
|
||||||
|
|
||||||
#support-string {
|
#support-string {
|
||||||
height: 16rem;
|
height: 16rem;
|
||||||
|
|
26
src/static/scripts/admin.js
gevendort
26
src/static/scripts/admin.js
gevendort
|
@ -1,4 +1,6 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
/* eslint-env es2017, browser */
|
||||||
|
/* exported BASE_URL, _post */
|
||||||
|
|
||||||
function getBaseUrl() {
|
function getBaseUrl() {
|
||||||
// If the base URL is `https://vaultwarden.example.com/base/path/`,
|
// If the base URL is `https://vaultwarden.example.com/base/path/`,
|
||||||
|
@ -26,6 +28,8 @@ function msg(text, reload_page = true) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function _post(url, successMsg, errMsg, body, reload_page = true) {
|
function _post(url, successMsg, errMsg, body, reload_page = true) {
|
||||||
|
let respStatus;
|
||||||
|
let respStatusText;
|
||||||
fetch(url, {
|
fetch(url, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: body,
|
body: body,
|
||||||
|
@ -33,22 +37,30 @@ function _post(url, successMsg, errMsg, body, reload_page = true) {
|
||||||
credentials: "same-origin",
|
credentials: "same-origin",
|
||||||
headers: { "Content-Type": "application/json" }
|
headers: { "Content-Type": "application/json" }
|
||||||
}).then( resp => {
|
}).then( resp => {
|
||||||
if (resp.ok) { msg(successMsg, reload_page); return Promise.reject({error: false}); }
|
if (resp.ok) {
|
||||||
const respStatus = resp.status;
|
msg(successMsg, reload_page);
|
||||||
const respStatusText = resp.statusText;
|
// Abuse the catch handler by setting error to false and continue
|
||||||
|
return Promise.reject({error: false});
|
||||||
|
}
|
||||||
|
respStatus = resp.status;
|
||||||
|
respStatusText = resp.statusText;
|
||||||
return resp.text();
|
return resp.text();
|
||||||
}).then( respText => {
|
}).then( respText => {
|
||||||
try {
|
try {
|
||||||
const respJson = JSON.parse(respText);
|
const respJson = JSON.parse(respText);
|
||||||
return respJson ? respJson.ErrorModel.Message : "Unknown error";
|
if (respJson.ErrorModel && respJson.ErrorModel.Message) {
|
||||||
|
return respJson.ErrorModel.Message;
|
||||||
|
} else {
|
||||||
|
return Promise.reject({body:`${respStatus} - ${respStatusText}\n\nUnknown error`, error: true});
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return Promise.reject({body:respStatus + " - " + respStatusText, error: true});
|
return Promise.reject({body:`${respStatus} - ${respStatusText}\n\n[Catch] ${e}`, error: true});
|
||||||
}
|
}
|
||||||
}).then( apiMsg => {
|
}).then( apiMsg => {
|
||||||
msg(errMsg + "\n" + apiMsg, reload_page);
|
msg(`${errMsg}\n${apiMsg}`, reload_page);
|
||||||
}).catch( e => {
|
}).catch( e => {
|
||||||
if (e.error === false) { return true; }
|
if (e.error === false) { return true; }
|
||||||
else { msg(errMsg + "\n" + e.body, reload_page); }
|
else { msg(`${errMsg}\n${e.body}`, reload_page); }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
20
src/static/scripts/admin_diagnostics.js
gevendort
20
src/static/scripts/admin_diagnostics.js
gevendort
|
@ -1,4 +1,6 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
/* eslint-env es2017, browser */
|
||||||
|
/* global BASE_URL:readable, BSN:readable */
|
||||||
|
|
||||||
var dnsCheck = false;
|
var dnsCheck = false;
|
||||||
var timeCheck = false;
|
var timeCheck = false;
|
||||||
|
@ -65,7 +67,7 @@ function checkVersions(platform, installed, latest, commit=null) {
|
||||||
|
|
||||||
// ================================
|
// ================================
|
||||||
// Generate support string to be pasted on github or the forum
|
// Generate support string to be pasted on github or the forum
|
||||||
async function generateSupportString(dj) {
|
async function generateSupportString(event, dj) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
|
||||||
|
@ -114,7 +116,7 @@ async function generateSupportString(dj) {
|
||||||
document.getElementById("copy-support").classList.remove("d-none");
|
document.getElementById("copy-support").classList.remove("d-none");
|
||||||
}
|
}
|
||||||
|
|
||||||
function copyToClipboard() {
|
function copyToClipboard(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
|
||||||
|
@ -208,12 +210,18 @@ function init(dj) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// onLoad events
|
// onLoad events
|
||||||
document.addEventListener("DOMContentLoaded", (/*event*/) => {
|
document.addEventListener("DOMContentLoaded", (event) => {
|
||||||
const diag_json = JSON.parse(document.getElementById("diagnostics_json").innerText);
|
const diag_json = JSON.parse(document.getElementById("diagnostics_json").innerText);
|
||||||
init(diag_json);
|
init(diag_json);
|
||||||
|
|
||||||
document.getElementById("gen-support").addEventListener("click", () => {
|
const btnGenSupport = document.getElementById("gen-support");
|
||||||
generateSupportString(diag_json);
|
if (btnGenSupport) {
|
||||||
|
btnGenSupport.addEventListener("click", () => {
|
||||||
|
generateSupportString(event, diag_json);
|
||||||
});
|
});
|
||||||
document.getElementById("copy-support").addEventListener("click", copyToClipboard);
|
}
|
||||||
|
const btnCopySupport = document.getElementById("copy-support");
|
||||||
|
if (btnCopySupport) {
|
||||||
|
btnCopySupport.addEventListener("click", copyToClipboard);
|
||||||
|
}
|
||||||
});
|
});
|
26
src/static/scripts/admin_organizations.js
gevendort
26
src/static/scripts/admin_organizations.js
gevendort
|
@ -1,6 +1,8 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
/* eslint-env es2017, browser, jquery */
|
||||||
|
/* global _post:readable, BASE_URL:readable, reload:readable, jdenticon:readable */
|
||||||
|
|
||||||
function deleteOrganization() {
|
function deleteOrganization(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
const org_uuid = event.target.dataset.vwOrgUuid;
|
const org_uuid = event.target.dataset.vwOrgUuid;
|
||||||
|
@ -28,9 +30,22 @@ function deleteOrganization() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function initActions() {
|
||||||
|
document.querySelectorAll("button[vw-delete-organization]").forEach(btn => {
|
||||||
|
btn.addEventListener("click", deleteOrganization);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (jdenticon) {
|
||||||
|
jdenticon();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// onLoad events
|
// onLoad events
|
||||||
document.addEventListener("DOMContentLoaded", (/*event*/) => {
|
document.addEventListener("DOMContentLoaded", (/*event*/) => {
|
||||||
jQuery("#orgs-table").DataTable({
|
jQuery("#orgs-table").DataTable({
|
||||||
|
"drawCallback": function() {
|
||||||
|
initActions();
|
||||||
|
},
|
||||||
"stateSave": true,
|
"stateSave": true,
|
||||||
"responsive": true,
|
"responsive": true,
|
||||||
"lengthMenu": [
|
"lengthMenu": [
|
||||||
|
@ -46,9 +61,10 @@ document.addEventListener("DOMContentLoaded", (/*event*/) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add click events for organization actions
|
// Add click events for organization actions
|
||||||
document.querySelectorAll("button[vw-delete-organization]").forEach(btn => {
|
initActions();
|
||||||
btn.addEventListener("click", deleteOrganization);
|
|
||||||
});
|
|
||||||
|
|
||||||
document.getElementById("reload").addEventListener("click", reload);
|
const btnReload = document.getElementById("reload");
|
||||||
|
if (btnReload) {
|
||||||
|
btnReload.addEventListener("click", reload);
|
||||||
|
}
|
||||||
});
|
});
|
55
src/static/scripts/admin_settings.js
gevendort
55
src/static/scripts/admin_settings.js
gevendort
|
@ -1,6 +1,8 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
/* eslint-env es2017, browser */
|
||||||
|
/* global _post:readable, BASE_URL:readable */
|
||||||
|
|
||||||
function smtpTest() {
|
function smtpTest(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
if (formHasChanges(config_form)) {
|
if (formHasChanges(config_form)) {
|
||||||
|
@ -41,7 +43,7 @@ function getFormData() {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveConfig() {
|
function saveConfig(event) {
|
||||||
const data = JSON.stringify(getFormData());
|
const data = JSON.stringify(getFormData());
|
||||||
_post(`${BASE_URL}/admin/config/`,
|
_post(`${BASE_URL}/admin/config/`,
|
||||||
"Config saved correctly",
|
"Config saved correctly",
|
||||||
|
@ -51,7 +53,7 @@ function saveConfig() {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
function deleteConf() {
|
function deleteConf(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
const input = prompt(
|
const input = prompt(
|
||||||
|
@ -68,7 +70,7 @@ function deleteConf() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function backupDatabase() {
|
function backupDatabase(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
_post(`${BASE_URL}/admin/config/backup_db`,
|
_post(`${BASE_URL}/admin/config/backup_db`,
|
||||||
|
@ -94,24 +96,26 @@ function formHasChanges(form) {
|
||||||
|
|
||||||
// This function will prevent submitting a from when someone presses enter.
|
// This function will prevent submitting a from when someone presses enter.
|
||||||
function preventFormSubmitOnEnter(form) {
|
function preventFormSubmitOnEnter(form) {
|
||||||
form.onkeypress = function(e) {
|
if (form) {
|
||||||
const key = e.charCode || e.keyCode || 0;
|
form.addEventListener("keypress", (event) => {
|
||||||
if (key == 13) {
|
if (event.key == "Enter") {
|
||||||
e.preventDefault();
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// This function will hook into the smtp-test-email input field and will call the smtpTest() function when enter is pressed.
|
// This function will hook into the smtp-test-email input field and will call the smtpTest() function when enter is pressed.
|
||||||
function submitTestEmailOnEnter() {
|
function submitTestEmailOnEnter() {
|
||||||
const smtp_test_email_input = document.getElementById("smtp-test-email");
|
const smtp_test_email_input = document.getElementById("smtp-test-email");
|
||||||
smtp_test_email_input.onkeypress = function(e) {
|
if (smtp_test_email_input) {
|
||||||
const key = e.charCode || e.keyCode || 0;
|
smtp_test_email_input.addEventListener("keypress", (event) => {
|
||||||
if (key == 13) {
|
if (event.key == "Enter") {
|
||||||
e.preventDefault();
|
event.preventDefault();
|
||||||
smtpTest();
|
smtpTest(event);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Colorize some settings which are high risk
|
// Colorize some settings which are high risk
|
||||||
|
@ -124,11 +128,11 @@ function colorRiskSettings() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleVis(evt) {
|
function toggleVis(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
|
||||||
const elem = document.getElementById(evt.target.dataset.vwPwToggle);
|
const elem = document.getElementById(event.target.dataset.vwPwToggle);
|
||||||
const type = elem.getAttribute("type");
|
const type = elem.getAttribute("type");
|
||||||
if (type === "text") {
|
if (type === "text") {
|
||||||
elem.setAttribute("type", "password");
|
elem.setAttribute("type", "password");
|
||||||
|
@ -146,10 +150,12 @@ function masterCheck(check_id, inputs_query) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const checkbox = document.getElementById(check_id);
|
const checkbox = document.getElementById(check_id);
|
||||||
|
if (checkbox) {
|
||||||
const onChange = onChanged(checkbox, inputs_query);
|
const onChange = onChanged(checkbox, inputs_query);
|
||||||
onChange(); // Trigger the event initially
|
onChange(); // Trigger the event initially
|
||||||
checkbox.addEventListener("change", onChange);
|
checkbox.addEventListener("change", onChange);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const config_form = document.getElementById("config-form");
|
const config_form = document.getElementById("config-form");
|
||||||
|
|
||||||
|
@ -172,9 +178,18 @@ document.addEventListener("DOMContentLoaded", (/*event*/) => {
|
||||||
password_toggle_btn.addEventListener("click", toggleVis);
|
password_toggle_btn.addEventListener("click", toggleVis);
|
||||||
});
|
});
|
||||||
|
|
||||||
document.getElementById("backupDatabase").addEventListener("click", backupDatabase);
|
const btnBackupDatabase = document.getElementById("backupDatabase");
|
||||||
document.getElementById("deleteConf").addEventListener("click", deleteConf);
|
if (btnBackupDatabase) {
|
||||||
document.getElementById("smtpTest").addEventListener("click", smtpTest);
|
btnBackupDatabase.addEventListener("click", backupDatabase);
|
||||||
|
}
|
||||||
|
const btnDeleteConf = document.getElementById("deleteConf");
|
||||||
|
if (btnDeleteConf) {
|
||||||
|
btnDeleteConf.addEventListener("click", deleteConf);
|
||||||
|
}
|
||||||
|
const btnSmtpTest = document.getElementById("smtpTest");
|
||||||
|
if (btnSmtpTest) {
|
||||||
|
btnSmtpTest.addEventListener("click", smtpTest);
|
||||||
|
}
|
||||||
|
|
||||||
config_form.addEventListener("submit", saveConfig);
|
config_form.addEventListener("submit", saveConfig);
|
||||||
});
|
});
|
91
src/static/scripts/admin_users.js
gevendort
91
src/static/scripts/admin_users.js
gevendort
|
@ -1,6 +1,8 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
/* eslint-env es2017, browser, jquery */
|
||||||
|
/* global _post:readable, BASE_URL:readable, reload:readable, jdenticon:readable */
|
||||||
|
|
||||||
function deleteUser() {
|
function deleteUser(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
const id = event.target.parentNode.dataset.vwUserUuid;
|
const id = event.target.parentNode.dataset.vwUserUuid;
|
||||||
|
@ -22,7 +24,7 @@ function deleteUser() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function remove2fa() {
|
function remove2fa(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
const id = event.target.parentNode.dataset.vwUserUuid;
|
const id = event.target.parentNode.dataset.vwUserUuid;
|
||||||
|
@ -36,7 +38,7 @@ function remove2fa() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function deauthUser() {
|
function deauthUser(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
const id = event.target.parentNode.dataset.vwUserUuid;
|
const id = event.target.parentNode.dataset.vwUserUuid;
|
||||||
|
@ -50,7 +52,7 @@ function deauthUser() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function disableUser() {
|
function disableUser(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
const id = event.target.parentNode.dataset.vwUserUuid;
|
const id = event.target.parentNode.dataset.vwUserUuid;
|
||||||
|
@ -68,7 +70,7 @@ function disableUser() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function enableUser() {
|
function enableUser(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
const id = event.target.parentNode.dataset.vwUserUuid;
|
const id = event.target.parentNode.dataset.vwUserUuid;
|
||||||
|
@ -86,7 +88,7 @@ function enableUser() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateRevisions() {
|
function updateRevisions(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
_post(`${BASE_URL}/admin/users/update_revision`,
|
_post(`${BASE_URL}/admin/users/update_revision`,
|
||||||
|
@ -95,7 +97,7 @@ function updateRevisions() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function inviteUser() {
|
function inviteUser(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
const email = document.getElementById("inviteEmail");
|
const email = document.getElementById("inviteEmail");
|
||||||
|
@ -182,7 +184,7 @@ userOrgTypeDialog.addEventListener("hide.bs.modal", function() {
|
||||||
document.getElementById("userOrgTypeOrgUuid").value = "";
|
document.getElementById("userOrgTypeOrgUuid").value = "";
|
||||||
}, false);
|
}, false);
|
||||||
|
|
||||||
function updateUserOrgType() {
|
function updateUserOrgType(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
|
||||||
|
@ -195,26 +197,7 @@ function updateUserOrgType() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// onLoad events
|
function initUserTable() {
|
||||||
document.addEventListener("DOMContentLoaded", (/*event*/) => {
|
|
||||||
jQuery("#users-table").DataTable({
|
|
||||||
"stateSave": true,
|
|
||||||
"responsive": true,
|
|
||||||
"lengthMenu": [
|
|
||||||
[-1, 5, 10, 25, 50],
|
|
||||||
["All", 5, 10, 25, 50]
|
|
||||||
],
|
|
||||||
"pageLength": -1, // Default show all
|
|
||||||
"columnDefs": [{
|
|
||||||
"targets": [1, 2],
|
|
||||||
"type": "date-iso"
|
|
||||||
}, {
|
|
||||||
"targets": 6,
|
|
||||||
"searchable": false,
|
|
||||||
"orderable": false
|
|
||||||
}]
|
|
||||||
});
|
|
||||||
|
|
||||||
// Color all the org buttons per type
|
// Color all the org buttons per type
|
||||||
document.querySelectorAll("button[data-vw-org-type]").forEach(function(e) {
|
document.querySelectorAll("button[data-vw-org-type]").forEach(function(e) {
|
||||||
const orgType = ORG_TYPES[e.dataset.vwOrgType];
|
const orgType = ORG_TYPES[e.dataset.vwOrgType];
|
||||||
|
@ -222,7 +205,6 @@ document.addEventListener("DOMContentLoaded", (/*event*/) => {
|
||||||
e.title = orgType.name;
|
e.title = orgType.name;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add click events for user actions
|
|
||||||
document.querySelectorAll("button[vw-remove2fa]").forEach(btn => {
|
document.querySelectorAll("button[vw-remove2fa]").forEach(btn => {
|
||||||
btn.addEventListener("click", remove2fa);
|
btn.addEventListener("click", remove2fa);
|
||||||
});
|
});
|
||||||
|
@ -239,8 +221,51 @@ document.addEventListener("DOMContentLoaded", (/*event*/) => {
|
||||||
btn.addEventListener("click", enableUser);
|
btn.addEventListener("click", enableUser);
|
||||||
});
|
});
|
||||||
|
|
||||||
document.getElementById("updateRevisions").addEventListener("click", updateRevisions);
|
if (jdenticon) {
|
||||||
document.getElementById("reload").addEventListener("click", reload);
|
jdenticon();
|
||||||
document.getElementById("userOrgTypeForm").addEventListener("submit", updateUserOrgType);
|
}
|
||||||
document.getElementById("inviteUserForm").addEventListener("submit", inviteUser);
|
}
|
||||||
|
|
||||||
|
// onLoad events
|
||||||
|
document.addEventListener("DOMContentLoaded", (/*event*/) => {
|
||||||
|
jQuery("#users-table").DataTable({
|
||||||
|
"drawCallback": function() {
|
||||||
|
initUserTable();
|
||||||
|
},
|
||||||
|
"stateSave": true,
|
||||||
|
"responsive": true,
|
||||||
|
"lengthMenu": [
|
||||||
|
[-1, 2, 5, 10, 25, 50],
|
||||||
|
["All", 2, 5, 10, 25, 50]
|
||||||
|
],
|
||||||
|
"pageLength": 2, // Default show all
|
||||||
|
"columnDefs": [{
|
||||||
|
"targets": [1, 2],
|
||||||
|
"type": "date-iso"
|
||||||
|
}, {
|
||||||
|
"targets": 6,
|
||||||
|
"searchable": false,
|
||||||
|
"orderable": false
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add click events for user actions
|
||||||
|
initUserTable();
|
||||||
|
|
||||||
|
const btnUpdateRevisions = document.getElementById("updateRevisions");
|
||||||
|
if (btnUpdateRevisions) {
|
||||||
|
btnUpdateRevisions.addEventListener("click", updateRevisions);
|
||||||
|
}
|
||||||
|
const btnReload = document.getElementById("reload");
|
||||||
|
if (btnReload) {
|
||||||
|
btnReload.addEventListener("click", reload);
|
||||||
|
}
|
||||||
|
const btnUserOrgTypeForm = document.getElementById("userOrgTypeForm");
|
||||||
|
if (btnUserOrgTypeForm) {
|
||||||
|
btnUserOrgTypeForm.addEventListener("submit", updateUserOrgType);
|
||||||
|
}
|
||||||
|
const btnInviteUserForm = document.getElementById("inviteUserForm");
|
||||||
|
if (btnInviteUserForm) {
|
||||||
|
btnInviteUserForm.addEventListener("submit", inviteUser);
|
||||||
|
}
|
||||||
});
|
});
|
|
@ -1,5 +1,5 @@
|
||||||
/*!
|
/*!
|
||||||
* jQuery JavaScript Library v3.6.2 -ajax,-ajax/jsonp,-ajax/load,-ajax/script,-ajax/var/location,-ajax/var/nonce,-ajax/var/rquery,-ajax/xhr,-manipulation/_evalUrl,-deprecated/ajax-event-alias,-effects,-effects/Tween,-effects/animatedSelector
|
* jQuery JavaScript Library v3.6.3 -ajax,-ajax/jsonp,-ajax/load,-ajax/script,-ajax/var/location,-ajax/var/nonce,-ajax/var/rquery,-ajax/xhr,-manipulation/_evalUrl,-deprecated/ajax-event-alias,-effects,-effects/Tween,-effects/animatedSelector
|
||||||
* https://jquery.com/
|
* https://jquery.com/
|
||||||
*
|
*
|
||||||
* Includes Sizzle.js
|
* Includes Sizzle.js
|
||||||
|
@ -9,7 +9,7 @@
|
||||||
* Released under the MIT license
|
* Released under the MIT license
|
||||||
* https://jquery.org/license
|
* https://jquery.org/license
|
||||||
*
|
*
|
||||||
* Date: 2022-12-13T14:56Z
|
* Date: 2022-12-20T21:28Z
|
||||||
*/
|
*/
|
||||||
( function( global, factory ) {
|
( function( global, factory ) {
|
||||||
|
|
||||||
|
@ -151,7 +151,7 @@ function toType( obj ) {
|
||||||
|
|
||||||
|
|
||||||
var
|
var
|
||||||
version = "3.6.2 -ajax,-ajax/jsonp,-ajax/load,-ajax/script,-ajax/var/location,-ajax/var/nonce,-ajax/var/rquery,-ajax/xhr,-manipulation/_evalUrl,-deprecated/ajax-event-alias,-effects,-effects/Tween,-effects/animatedSelector",
|
version = "3.6.3 -ajax,-ajax/jsonp,-ajax/load,-ajax/script,-ajax/var/location,-ajax/var/nonce,-ajax/var/rquery,-ajax/xhr,-manipulation/_evalUrl,-deprecated/ajax-event-alias,-effects,-effects/Tween,-effects/animatedSelector",
|
||||||
|
|
||||||
// Define a local copy of jQuery
|
// Define a local copy of jQuery
|
||||||
jQuery = function( selector, context ) {
|
jQuery = function( selector, context ) {
|
||||||
|
@ -522,14 +522,14 @@ function isArrayLike( obj ) {
|
||||||
}
|
}
|
||||||
var Sizzle =
|
var Sizzle =
|
||||||
/*!
|
/*!
|
||||||
* Sizzle CSS Selector Engine v2.3.8
|
* Sizzle CSS Selector Engine v2.3.9
|
||||||
* https://sizzlejs.com/
|
* https://sizzlejs.com/
|
||||||
*
|
*
|
||||||
* Copyright JS Foundation and other contributors
|
* Copyright JS Foundation and other contributors
|
||||||
* Released under the MIT license
|
* Released under the MIT license
|
||||||
* https://js.foundation/
|
* https://js.foundation/
|
||||||
*
|
*
|
||||||
* Date: 2022-11-16
|
* Date: 2022-12-19
|
||||||
*/
|
*/
|
||||||
( function( window ) {
|
( function( window ) {
|
||||||
var i,
|
var i,
|
||||||
|
@ -890,7 +890,7 @@ function Sizzle( selector, context, results, seed ) {
|
||||||
if ( support.cssSupportsSelector &&
|
if ( support.cssSupportsSelector &&
|
||||||
|
|
||||||
// eslint-disable-next-line no-undef
|
// eslint-disable-next-line no-undef
|
||||||
!CSS.supports( "selector(" + newSelector + ")" ) ) {
|
!CSS.supports( "selector(:is(" + newSelector + "))" ) ) {
|
||||||
|
|
||||||
// Support: IE 11+
|
// Support: IE 11+
|
||||||
// Throw to get to the same code path as an error directly in qSA.
|
// Throw to get to the same code path as an error directly in qSA.
|
||||||
|
@ -1492,9 +1492,8 @@ setDocument = Sizzle.setDocument = function( node ) {
|
||||||
// `:has()` uses a forgiving selector list as an argument so our regular
|
// `:has()` uses a forgiving selector list as an argument so our regular
|
||||||
// `try-catch` mechanism fails to catch `:has()` with arguments not supported
|
// `try-catch` mechanism fails to catch `:has()` with arguments not supported
|
||||||
// natively like `:has(:contains("Foo"))`. Where supported & spec-compliant,
|
// natively like `:has(:contains("Foo"))`. Where supported & spec-compliant,
|
||||||
// we now use `CSS.supports("selector(SELECTOR_TO_BE_TESTED)")` but outside
|
// we now use `CSS.supports("selector(:is(SELECTOR_TO_BE_TESTED))")`, but
|
||||||
// that, let's mark `:has` as buggy to always use jQuery traversal for
|
// outside that we mark `:has` as buggy.
|
||||||
// `:has()`.
|
|
||||||
rbuggyQSA.push( ":has" );
|
rbuggyQSA.push( ":has" );
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,10 +5,10 @@
|
||||||
<table id="orgs-table" class="table table-sm table-striped table-hover">
|
<table id="orgs-table" class="table table-sm table-striped table-hover">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Organization</th>
|
<th class="vw-org-details">Organization</th>
|
||||||
<th>Users</th>
|
<th class="vw-users">Users</th>
|
||||||
<th>Items</th>
|
<th class="vw-items">Items</th>
|
||||||
<th>Attachments</th>
|
<th class="vw-attachments">Attachments</th>
|
||||||
<th class="vw-actions">Actions</th>
|
<th class="vw-actions">Actions</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
@ -38,7 +38,7 @@
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</td>
|
</td>
|
||||||
<td class="text-end px-0 small">
|
<td class="text-end px-0 small">
|
||||||
<button type="button" class="btn btn-sm btn-link p-0 border-0" vw-delete-organization data-vw-org-uuid="{{jsesc Id no_quote}}" data-vw-org-name="{{jsesc Name no_quote}}" data-vw-billing-email="{{jsesc BillingEmail no_quote}}">Delete Organization</button>
|
<button type="button" class="btn btn-sm btn-link p-0 border-0 float-right" vw-delete-organization data-vw-org-uuid="{{jsesc Id no_quote}}" data-vw-org-name="{{jsesc Name no_quote}}" data-vw-billing-email="{{jsesc BillingEmail no_quote}}">Delete Organization</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
|
@ -53,7 +53,7 @@
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<link rel="stylesheet" href="{{urlpath}}/vw_static/datatables.css" />
|
<link rel="stylesheet" href="{{urlpath}}/vw_static/datatables.css" />
|
||||||
<script src="{{urlpath}}/vw_static/jquery-3.6.2.slim.js"></script>
|
<script src="{{urlpath}}/vw_static/jquery-3.6.3.slim.js"></script>
|
||||||
<script src="{{urlpath}}/vw_static/datatables.js"></script>
|
<script src="{{urlpath}}/vw_static/datatables.js"></script>
|
||||||
<script src="{{urlpath}}/vw_static/admin_organizations.js"></script>
|
<script src="{{urlpath}}/vw_static/admin_organizations.js"></script>
|
||||||
<script src="{{urlpath}}/vw_static/jdenticon.js"></script>
|
<script src="{{urlpath}}/vw_static/jdenticon.js"></script>
|
||||||
|
|
|
@ -47,7 +47,7 @@
|
||||||
<div class="row my-2 align-items-center pt-3 border-top" title="Send a test email to given email address">
|
<div class="row my-2 align-items-center pt-3 border-top" title="Send a test email to given email address">
|
||||||
<label for="smtp-test-email" class="col-sm-3 col-form-label">Test SMTP</label>
|
<label for="smtp-test-email" class="col-sm-3 col-form-label">Test SMTP</label>
|
||||||
<div class="col-sm-8 input-group">
|
<div class="col-sm-8 input-group">
|
||||||
<input class="form-control" id="smtp-test-email" type="email" placeholder="Enter test email" required>
|
<input class="form-control" id="smtp-test-email" type="email" placeholder="Enter test email" required spellcheck="false">
|
||||||
<button type="button" class="btn btn-outline-primary input-group-text" id="smtpTest">Send test email</button>
|
<button type="button" class="btn btn-outline-primary input-group-text" id="smtpTest">Send test email</button>
|
||||||
<div class="invalid-tooltip">Please provide a valid email address</div>
|
<div class="invalid-tooltip">Please provide a valid email address</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -85,7 +85,7 @@
|
||||||
<input readonly class="form-control" id="input_{{name}}" type="password" value="{{value}}" {{#if default}} placeholder="Default: {{default}}" {{/if}}>
|
<input readonly class="form-control" id="input_{{name}}" type="password" value="{{value}}" {{#if default}} placeholder="Default: {{default}}" {{/if}}>
|
||||||
<button class="btn btn-outline-secondary" type="button" data-vw-pw-toggle="input_{{name}}">Show/hide</button>
|
<button class="btn btn-outline-secondary" type="button" data-vw-pw-toggle="input_{{name}}">Show/hide</button>
|
||||||
{{else}}
|
{{else}}
|
||||||
<input readonly class="form-control" id="input_{{name}}" type="{{type}}" value="{{value}}" {{#if default}} placeholder="Default: {{default}}" {{/if}}>
|
<input readonly class="form-control" id="input_{{name}}" type="{{type}}" value="{{value}}" {{#if default}} placeholder="Default: {{default}}" {{/if}} spellcheck="false">
|
||||||
{{#case type "password"}}
|
{{#case type "password"}}
|
||||||
<button class="btn btn-outline-secondary" type="button" data-vw-pw-toggle="input_{{name}}">Show/hide</button>
|
<button class="btn btn-outline-secondary" type="button" data-vw-pw-toggle="input_{{name}}">Show/hide</button>
|
||||||
{{/case}}
|
{{/case}}
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<table id="users-table" class="table table-sm table-striped table-hover">
|
<table id="users-table" class="table table-sm table-striped table-hover">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>User</th>
|
<th class="vw-account-details">User</th>
|
||||||
<th class="vw-created-at">Created at</th>
|
<th class="vw-created-at">Created at</th>
|
||||||
<th class="vw-last-active">Last Active</th>
|
<th class="vw-last-active">Last Active</th>
|
||||||
<th class="vw-items">Items</th>
|
<th class="vw-items">Items</th>
|
||||||
|
@ -63,14 +63,14 @@
|
||||||
<td class="text-end px-0 small">
|
<td class="text-end px-0 small">
|
||||||
<span data-vw-user-uuid="{{jsesc Id no_quote}}" data-vw-user-email="{{jsesc Email no_quote}}">
|
<span data-vw-user-uuid="{{jsesc Id no_quote}}" data-vw-user-email="{{jsesc Email no_quote}}">
|
||||||
{{#if TwoFactorEnabled}}
|
{{#if TwoFactorEnabled}}
|
||||||
<button type="button" class="btn btn-sm btn-link p-0 border-0" vw-remove2fa>Remove all 2FA</button>
|
<button type="button" class="btn btn-sm btn-link p-0 border-0 float-right" vw-remove2fa>Remove all 2FA</button>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
<button type="button" class="btn btn-sm btn-link p-0 border-0" vw-deauth-user>Deauthorize sessions</button>
|
<button type="button" class="btn btn-sm btn-link p-0 border-0 float-right" vw-deauth-user>Deauthorize sessions</button>
|
||||||
<button type="button" class="btn btn-sm btn-link p-0 border-0" vw-delete-user>Delete User</button>
|
<button type="button" class="btn btn-sm btn-link p-0 border-0 float-right" vw-delete-user>Delete User</button>
|
||||||
{{#if user_enabled}}
|
{{#if user_enabled}}
|
||||||
<button type="button" class="btn btn-sm btn-link p-0 border-0" vw-disable-user>Disable User</button>
|
<button type="button" class="btn btn-sm btn-link p-0 border-0 float-right" vw-disable-user>Disable User</button>
|
||||||
{{else}}
|
{{else}}
|
||||||
<button type="button" class="btn btn-sm btn-link p-0 border-0" vw-enable-user>Enable User</button>
|
<button type="button" class="btn btn-sm btn-link p-0 border-0 float-right" vw-enable-user>Enable User</button>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
|
@ -96,7 +96,7 @@
|
||||||
<small>Email:</small>
|
<small>Email:</small>
|
||||||
|
|
||||||
<form class="form-inline input-group w-50" id="inviteUserForm">
|
<form class="form-inline input-group w-50" id="inviteUserForm">
|
||||||
<input type="email" class="form-control me-2" id="inviteEmail" placeholder="Enter email" required>
|
<input type="email" class="form-control me-2" id="inviteEmail" placeholder="Enter email" required spellcheck="false">
|
||||||
<button type="submit" class="btn btn-primary">Invite</button>
|
<button type="submit" class="btn btn-primary">Invite</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
@ -137,7 +137,7 @@
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<link rel="stylesheet" href="{{urlpath}}/vw_static/datatables.css" />
|
<link rel="stylesheet" href="{{urlpath}}/vw_static/datatables.css" />
|
||||||
<script src="{{urlpath}}/vw_static/jquery-3.6.2.slim.js"></script>
|
<script src="{{urlpath}}/vw_static/jquery-3.6.3.slim.js"></script>
|
||||||
<script src="{{urlpath}}/vw_static/datatables.js"></script>
|
<script src="{{urlpath}}/vw_static/datatables.js"></script>
|
||||||
<script src="{{urlpath}}/vw_static/admin_users.js"></script>
|
<script src="{{urlpath}}/vw_static/admin_users.js"></script>
|
||||||
<script src="{{urlpath}}/vw_static/jdenticon.js"></script>
|
<script src="{{urlpath}}/vw_static/jdenticon.js"></script>
|
||||||
|
|
6
src/static/templates/email/admin_reset_password.hbs
Normale Datei
6
src/static/templates/email/admin_reset_password.hbs
Normale Datei
|
@ -0,0 +1,6 @@
|
||||||
|
Master Password Has Been Changed
|
||||||
|
<!---------------->
|
||||||
|
The master password for {{user_name}} has been changed by an administrator in your {{org_name}} organization. If you did not initiate this request, please reach out to your administrator immediately.
|
||||||
|
|
||||||
|
===
|
||||||
|
Github: https://github.com/dani-garcia/vaultwarden
|
11
src/static/templates/email/admin_reset_password.html.hbs
Normale Datei
11
src/static/templates/email/admin_reset_password.html.hbs
Normale Datei
|
@ -0,0 +1,11 @@
|
||||||
|
Master Password Has Been Changed
|
||||||
|
<!---------------->
|
||||||
|
{{> email/email_header }}
|
||||||
|
<table width="100%" cellpadding="0" cellspacing="0" style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
|
||||||
|
<tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
|
||||||
|
<td class="content-block" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; margin: 0; -webkit-font-smoothing: antialiased; padding: 0 0 10px; -webkit-text-size-adjust: none;" valign="top">
|
||||||
|
The master password for <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;">{{user_name}}</b> has been changed by an administrator in your <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. If you did not initiate this request, please reach out to your administrator immediately.
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
{{> email/email_footer }}
|
Laden …
In neuem Issue referenzieren