Merge branch 'main' into fix-libressl-332

pull/1636/head
rkowalewski 4 years ago committed by GitHub
commit 48482fece0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -4,6 +4,8 @@ target
# Data folder # Data folder
data data
.env .env
.env.template
.gitattributes
# IDE files # IDE files
.vscode .vscode

@ -0,0 +1,23 @@
# EditorConfig is awesome: https://EditorConfig.org
# top-most EditorConfig file
root = true
[*]
end_of_line = lf
charset = utf-8
[*.{rs,py}]
indent_style = space
indent_size = 4
trim_trailing_whitespace = true
insert_final_newline = true
[*.{yml,yaml}]
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
insert_final_newline = true
[Makefile]
indent_style = tab

@ -1,4 +1,4 @@
## Bitwarden_RS Configuration File ## Vaultwarden Configuration File
## Uncomment any of the following lines to change the defaults ## Uncomment any of the following lines to change the defaults
## ##
## Be aware that most of these settings will be overridden if they were changed ## Be aware that most of these settings will be overridden if they were changed
@ -36,9 +36,9 @@
## Automatically reload the templates for every request, slow, use only for development ## Automatically reload the templates for every request, slow, use only for development
# RELOAD_TEMPLATES=false # RELOAD_TEMPLATES=false
## Client IP Header, used to identify the IP of the client, defaults to "X-Client-IP" ## Client IP Header, used to identify the IP of the client, defaults to "X-Real-IP"
## Set to the string "none" (without quotes), to disable any headers and just use the remote IP ## Set to the string "none" (without quotes), to disable any headers and just use the remote IP
# IP_HEADER=X-Client-IP # IP_HEADER=X-Real-IP
## Cache time-to-live for successfully obtained icons, in seconds (0 is "forever") ## Cache time-to-live for successfully obtained icons, in seconds (0 is "forever")
# ICON_CACHE_TTL=2592000 # ICON_CACHE_TTL=2592000
@ -56,6 +56,23 @@
# WEBSOCKET_ADDRESS=0.0.0.0 # WEBSOCKET_ADDRESS=0.0.0.0
# WEBSOCKET_PORT=3012 # WEBSOCKET_PORT=3012
## Job scheduler settings
##
## Job schedules use a cron-like syntax (as parsed by https://crates.io/crates/cron),
## and are always in terms of UTC time (regardless of your local time zone settings).
##
## How often (in ms) the job scheduler thread checks for jobs that need running.
## Set to 0 to globally disable scheduled jobs.
# JOB_POLL_INTERVAL_MS=30000
##
## Cron schedule of the job that checks for Sends past their deletion date.
## Defaults to hourly (5 minutes after the hour). Set blank to disable this job.
# SEND_PURGE_SCHEDULE="0 5 * * * *"
##
## Cron schedule of the job that checks for trashed items to delete permanently.
## Defaults to daily (5 minutes after midnight). Set blank to disable this job.
# TRASH_PURGE_SCHEDULE="0 5 0 * * *"
## Enable extended logging, which shows timestamps and targets in the logs ## Enable extended logging, which shows timestamps and targets in the logs
# EXTENDED_LOGGING=true # EXTENDED_LOGGING=true
@ -82,7 +99,7 @@
## Enable WAL for the DB ## Enable WAL for the DB
## Set to false to avoid enabling WAL during startup. ## Set to false to avoid enabling WAL during startup.
## Note that if the DB already has WAL enabled, you will also need to disable WAL in the DB, ## Note that if the DB already has WAL enabled, you will also need to disable WAL in the DB,
## this setting only prevents bitwarden_rs from automatically enabling it on start. ## this setting only prevents vaultwarden from automatically enabling it on start.
## Please read project wiki page about this setting first before changing the value as it can ## Please read project wiki page about this setting first before changing the value as it can
## cause performance degradation or might render the service unable to start. ## cause performance degradation or might render the service unable to start.
# ENABLE_DB_WAL=true # ENABLE_DB_WAL=true
@ -170,7 +187,7 @@
## Invitations org admins to invite users, even when signups are disabled ## Invitations org admins to invite users, even when signups are disabled
# INVITATIONS_ALLOWED=true # INVITATIONS_ALLOWED=true
## Name shown in the invitation emails that don't come from a specific organization ## Name shown in the invitation emails that don't come from a specific organization
# INVITATION_ORG_NAME=Bitwarden_RS # INVITATION_ORG_NAME=Vaultwarden
## Per-organization attachment limit (KB) ## Per-organization attachment limit (KB)
## Limit in kilobytes for an organization attachments, once the limit is exceeded it won't be possible to upload more ## Limit in kilobytes for an organization attachments, once the limit is exceeded it won't be possible to upload more
@ -242,8 +259,8 @@
## To make sure the email links are pointing to the correct host, set the DOMAIN variable. ## To make sure the email links are pointing to the correct host, set the DOMAIN variable.
## Note: if SMTP_USERNAME is specified, SMTP_PASSWORD is mandatory ## Note: if SMTP_USERNAME is specified, SMTP_PASSWORD is mandatory
# SMTP_HOST=smtp.domain.tld # SMTP_HOST=smtp.domain.tld
# SMTP_FROM=bitwarden-rs@domain.tld # SMTP_FROM=vaultwarden@domain.tld
# SMTP_FROM_NAME=Bitwarden_RS # SMTP_FROM_NAME=Vaultwarden
# SMTP_PORT=587 # Ports 587 (submission) and 25 (smtp) are standard without encryption and with encryption via STARTTLS (Explicit TLS). Port 465 is outdated and used with Implicit TLS. # SMTP_PORT=587 # Ports 587 (submission) and 25 (smtp) are standard without encryption and with encryption via STARTTLS (Explicit TLS). Port 465 is outdated and used with Implicit TLS.
# SMTP_SSL=true # (Explicit) - This variable by default configures Explicit STARTTLS, it will upgrade an insecure connection to a secure one. Unless SMTP_EXPLICIT_TLS is set to true. Either port 587 or 25 are default. # SMTP_SSL=true # (Explicit) - This variable by default configures Explicit STARTTLS, it will upgrade an insecure connection to a secure one. Unless SMTP_EXPLICIT_TLS is set to true. Either port 587 or 25 are default.
# SMTP_EXPLICIT_TLS=true # (Implicit) - N.B. This variable configures Implicit TLS. It's currently mislabelled (see bug #851) - SMTP_SSL Needs to be set to true for this option to work. Usually port 465 is used here. # SMTP_EXPLICIT_TLS=true # (Implicit) - N.B. This variable configures Implicit TLS. It's currently mislabelled (see bug #851) - SMTP_SSL Needs to be set to true for this option to work. Usually port 465 is used here.

2
.gitattributes vendored

@ -1,3 +1,3 @@
# Ignore vendored scripts in GitHub stats # Ignore vendored scripts in GitHub stats
src/static/* linguist-vendored src/static/scripts/* linguist-vendored

@ -1,6 +1,6 @@
--- ---
name: Bug report name: Bug report
about: Use this ONLY for bugs in bitwarden_rs itself. Use the Discourse forum (link below) to request features or get help with usage/configuration. If in doubt, use the forum. about: Use this ONLY for bugs in vaultwarden itself. Use the Discourse forum (link below) to request features or get help with usage/configuration. If in doubt, use the forum.
title: '' title: ''
labels: '' labels: ''
assignees: '' assignees: ''
@ -8,11 +8,11 @@ assignees: ''
--- ---
<!-- <!--
# ### # ###
NOTE: Please update to the latest version of bitwarden_rs before reporting an issue! NOTE: Please update to the latest version of vaultwarden before reporting an issue!
This saves you and us a lot of time and troubleshooting. This saves you and us a lot of time and troubleshooting.
See: See:
* https://github.com/dani-garcia/bitwarden_rs/issues/1180 * https://github.com/dani-garcia/vaultwarden/issues/1180
* https://github.com/dani-garcia/bitwarden_rs/wiki/Updating-the-bitwarden-image * https://github.com/dani-garcia/vaultwarden/wiki/Updating-the-vaultwarden-image
# ### # ###
--> -->
@ -37,9 +37,9 @@ such as passwords, IP addresses, and DNS names as appropriate.
--> -->
<!-- The version number, obtained from the logs (at startup) or the admin diagnostics page --> <!-- The version number, obtained from the logs (at startup) or the admin diagnostics page -->
<!-- This is NOT the version number shown on the web vault, which is versioned separately from bitwarden_rs --> <!-- This is NOT the version number shown on the web vault, which is versioned separately from vaultwarden -->
<!-- Remember to check if your issue exists on the latest version first! --> <!-- Remember to check if your issue exists on the latest version first! -->
* bitwarden_rs version: * vaultwarden version:
<!-- How the server was installed: Docker image, OS package, built from source, etc. --> <!-- How the server was installed: Docker image, OS package, built from source, etc. -->
* Install method: * Install method:
@ -54,7 +54,7 @@ such as passwords, IP addresses, and DNS names as appropriate.
### Steps to reproduce ### Steps to reproduce
<!-- Tell us how to reproduce this issue. What parameters did you set (differently from the defaults) <!-- Tell us how to reproduce this issue. What parameters did you set (differently from the defaults)
and how did you start bitwarden_rs? --> and how did you start vaultwarden? -->
### Expected behaviour ### Expected behaviour
<!-- Tell us what you expected to happen --> <!-- Tell us what you expected to happen -->

@ -3,6 +3,6 @@ contact_links:
- name: Discourse forum for bitwarden_rs - name: Discourse forum for bitwarden_rs
url: https://bitwardenrs.discourse.group/ url: https://bitwardenrs.discourse.group/
about: Use this forum to request features or get help with usage/configuration. about: Use this forum to request features or get help with usage/configuration.
- name: GitHub Discussions for bitwarden_rs - name: GitHub Discussions for vaultwarden
url: https://github.com/dani-garcia/bitwarden_rs/discussions url: https://github.com/dani-garcia/vaultwarden/discussions
about: An alternative to the Discourse forum, if this is easier for you. about: An alternative to the Discourse forum, if this is easier for you.

@ -2,23 +2,21 @@ name: Build
on: on:
push: push:
# Ignore when there are only changes done too one of these paths
paths-ignore:
- "**.md"
- "**.txt"
- "azure-pipelines.yml"
- "docker/**"
- "hooks/**"
- "tools/**"
pull_request: pull_request:
# Ignore when there are only changes done too one of these paths # Ignore when there are only changes done too one of these paths
paths-ignore: paths-ignore:
- "**.md" - "**.md"
- "**.txt" - "**.txt"
- ".dockerignore"
- ".env.template"
- ".gitattributes"
- ".gitignore"
- "azure-pipelines.yml" - "azure-pipelines.yml"
- "docker/**" - "docker/**"
- "hooks/**" - "hooks/**"
- "tools/**" - "tools/**"
- ".github/FUNDING.yml"
- ".github/ISSUE_TEMPLATE/**"
jobs: jobs:
build: build:
@ -91,7 +89,7 @@ jobs:
with: with:
profile: minimal profile: minimal
target: ${{ matrix.target-triple }} target: ${{ matrix.target-triple }}
components: clippy components: clippy, rustfmt
# End Uses the rust-toolchain file to determine version # End Uses the rust-toolchain file to determine version
@ -113,6 +111,15 @@ jobs:
# End Run cargo clippy # End Run cargo clippy
# Run cargo fmt
- name: '`cargo fmt`'
uses: actions-rs/cargo@v1
with:
command: fmt
args: --all -- --check
# End Run cargo fmt
# Build the binary # Build the binary
- name: '`cargo build --release --features ${{ matrix.features }} --target ${{ matrix.target-triple }}`' - name: '`cargo build --release --features ${{ matrix.features }} --target ${{ matrix.target-triple }}`'
uses: actions-rs/cargo@v1 uses: actions-rs/cargo@v1
@ -126,8 +133,8 @@ jobs:
- name: Upload artifact - name: Upload artifact
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v2
with: with:
name: bitwarden_rs-${{ matrix.target-triple }}${{ matrix.ext }} name: vaultwarden-${{ matrix.target-triple }}${{ matrix.ext }}
path: target/${{ matrix.target-triple }}/release/bitwarden_rs${{ matrix.ext }} path: target/${{ matrix.target-triple }}/release/vaultwarden${{ matrix.ext }}
# End Upload artifact to Github Actions # End Upload artifact to Github Actions
@ -138,7 +145,7 @@ jobs:
# uses: Shopify/upload-to-release@1 # uses: Shopify/upload-to-release@1
# if: startsWith(github.ref, 'refs/tags/') # if: startsWith(github.ref, 'refs/tags/')
# with: # with:
# name: bitwarden_rs-${{ matrix.target-triple }}${{ matrix.ext }} # name: vaultwarden-${{ matrix.target-triple }}${{ matrix.ext }}
# path: target/${{ matrix.target-triple }}/release/bitwarden_rs${{ matrix.ext }} # path: target/${{ matrix.target-triple }}/release/vaultwarden${{ matrix.ext }}
# repo-token: ${{ secrets.GITHUB_TOKEN }} # repo-token: ${{ secrets.GITHUB_TOKEN }}
# End Upload to github actions release # End Upload to github actions release

@ -1,6 +1,7 @@
name: Hadolint name: Hadolint
on: on:
push:
pull_request: pull_request:
# Ignore when there are only changes done too one of these paths # Ignore when there are only changes done too one of these paths
paths: paths:
@ -24,7 +25,7 @@ jobs:
sudo curl -L https://github.com/hadolint/hadolint/releases/download/v$HADOLINT_VERSION/hadolint-$(uname -s)-$(uname -m) -o /usr/local/bin/hadolint && \ sudo curl -L https://github.com/hadolint/hadolint/releases/download/v$HADOLINT_VERSION/hadolint-$(uname -s)-$(uname -m) -o /usr/local/bin/hadolint && \
sudo chmod +x /usr/local/bin/hadolint sudo chmod +x /usr/local/bin/hadolint
env: env:
HADOLINT_VERSION: 1.19.0 HADOLINT_VERSION: 2.0.0
# End Download hadolint # End Download hadolint
# Test Dockerfiles # Test Dockerfiles

472
Cargo.lock generated

@ -26,6 +26,35 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "alloc-no-stdlib"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5192ec435945d87bc2f70992b4d818154b5feede43c09fb7592146374eac90a6"
[[package]]
name = "alloc-stdlib"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "697ed7edc0f1711de49ce108c541623a0af97c6c60b2f6e2b65229847ac843c2"
dependencies = [
"alloc-no-stdlib",
]
[[package]]
name = "async-compression"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b72c1f1154e234325b50864a349b9c8e56939e266a4c307c0f159812df2f9537"
dependencies = [
"brotli",
"flate2",
"futures-core",
"memchr",
"pin-project-lite",
"tokio",
]
[[package]] [[package]]
name = "atty" name = "atty"
version = "0.2.14" version = "0.2.14"
@ -115,55 +144,6 @@ dependencies = [
"wyz", "wyz",
] ]
[[package]]
name = "bitwarden_rs"
version = "1.0.0"
dependencies = [
"backtrace",
"chashmap",
"chrono",
"chrono-tz",
"data-encoding",
"data-url",
"diesel",
"diesel_migrations",
"dotenv",
"fern",
"handlebars",
"html5ever",
"idna 0.2.2",
"jsonwebtoken",
"lettre",
"libsqlite3-sys",
"log 0.4.14",
"markup5ever_rcdom",
"multipart",
"newline-converter",
"num-derive",
"num-traits",
"oath",
"once_cell",
"openssl",
"parity-ws",
"paste",
"percent-encoding 2.1.0",
"pico-args",
"rand 0.8.3",
"regex",
"reqwest",
"ring",
"rmpv",
"rocket",
"rocket_contrib",
"serde",
"serde_json",
"syslog",
"time 0.2.26",
"u2f",
"uuid",
"yubico",
]
[[package]] [[package]]
name = "block-buffer" name = "block-buffer"
version = "0.7.3" version = "0.7.3"
@ -194,6 +174,27 @@ dependencies = [
"byte-tools 0.3.1", "byte-tools 0.3.1",
] ]
[[package]]
name = "brotli"
version = "3.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f29919120f08613aadcd4383764e00526fc9f18b6c0895814faeed0dd78613e"
dependencies = [
"alloc-no-stdlib",
"alloc-stdlib",
"brotli-decompressor",
]
[[package]]
name = "brotli-decompressor"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1052e1c3b8d4d80eb84a8b94f0a1498797b5fb96314c001156a1c761940ef4ec"
dependencies = [
"alloc-no-stdlib",
"alloc-stdlib",
]
[[package]] [[package]]
name = "buf_redux" name = "buf_redux"
version = "0.8.4" version = "0.8.4"
@ -298,9 +299,9 @@ dependencies = [
[[package]] [[package]]
name = "const_fn" name = "const_fn"
version = "0.4.5" version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28b9d6de7f49e22cf97ad17fc4036ece69300032f45f78f30b4a4482cdc3f4a6" checksum = "076a6803b0dacd6a88cfe64deba628b01533ff5ef265687e6938280c1afd0a28"
[[package]] [[package]]
name = "constant_time_eq" name = "constant_time_eq"
@ -341,6 +342,26 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8aebca1129a03dc6dc2b127edd729435bbc4a37e1d5f4d7513165089ceb02634" checksum = "8aebca1129a03dc6dc2b127edd729435bbc4a37e1d5f4d7513165089ceb02634"
[[package]]
name = "crc32fast"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a"
dependencies = [
"cfg-if 1.0.0",
]
[[package]]
name = "cron"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e009ed0b762cf7a967a34dfdc67d5967d3f828f12901d37081432c3dd1668f8f"
dependencies = [
"chrono",
"nom 4.1.1",
"once_cell",
]
[[package]] [[package]]
name = "crypto-mac" name = "crypto-mac"
version = "0.3.0" version = "0.3.0"
@ -399,9 +420,9 @@ version = "0.3.0"
source = "git+https://github.com/SergioBenitez/Devise.git?rev=e58b3ac9a#e58b3ac9afc3b6ff10a8aaf02a3e768a8f530089" source = "git+https://github.com/SergioBenitez/Devise.git?rev=e58b3ac9a#e58b3ac9afc3b6ff10a8aaf02a3e768a8f530089"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"proc-macro2 1.0.24", "proc-macro2 1.0.26",
"quote 1.0.9", "quote 1.0.9",
"syn 1.0.64", "syn 1.0.69",
] ]
[[package]] [[package]]
@ -427,9 +448,9 @@ version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "45f5098f628d02a7a0f68ddba586fb61e80edec3bdc1be3b921f4ceec60858d3" checksum = "45f5098f628d02a7a0f68ddba586fb61e80edec3bdc1be3b921f4ceec60858d3"
dependencies = [ dependencies = [
"proc-macro2 1.0.24", "proc-macro2 1.0.26",
"quote 1.0.9", "quote 1.0.9",
"syn 1.0.64", "syn 1.0.69",
] ]
[[package]] [[package]]
@ -491,6 +512,12 @@ version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f"
[[package]]
name = "either"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
[[package]] [[package]]
name = "encoding_rs" name = "encoding_rs"
version = "0.8.28" version = "0.8.28"
@ -525,6 +552,18 @@ dependencies = [
"syslog", "syslog",
] ]
[[package]]
name = "flate2"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd3aec53de10fe96d7d8c565eb17f2c687bb5518a2ec453b5b1252964526abe0"
dependencies = [
"cfg-if 1.0.0",
"crc32fast",
"libc",
"miniz_oxide",
]
[[package]] [[package]]
name = "fnv" name = "fnv"
version = "1.0.7" version = "1.0.7"
@ -596,9 +635,9 @@ dependencies = [
[[package]] [[package]]
name = "futures" name = "futures"
version = "0.3.13" version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f55667319111d593ba876406af7c409c0ebb44dc4be6132a783ccf163ea14c1" checksum = "a9d5813545e459ad3ca1bff9915e9ad7f1a47dc6a91b627ce321d5863b7dd253"
dependencies = [ dependencies = [
"futures-channel", "futures-channel",
"futures-core", "futures-core",
@ -611,9 +650,9 @@ dependencies = [
[[package]] [[package]]
name = "futures-channel" name = "futures-channel"
version = "0.3.13" version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c2dd2df839b57db9ab69c2c9d8f3e8c81984781937fe2807dc6dcf3b2ad2939" checksum = "ce79c6a52a299137a6013061e0cf0e688fce5d7f1bc60125f520912fdb29ec25"
dependencies = [ dependencies = [
"futures-core", "futures-core",
"futures-sink", "futures-sink",
@ -621,15 +660,15 @@ dependencies = [
[[package]] [[package]]
name = "futures-core" name = "futures-core"
version = "0.3.13" version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15496a72fabf0e62bdc3df11a59a3787429221dd0710ba8ef163d6f7a9112c94" checksum = "098cd1c6dda6ca01650f1a37a794245eb73181d0d4d4e955e2f3c37db7af1815"
[[package]] [[package]]
name = "futures-executor" name = "futures-executor"
version = "0.3.13" version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "891a4b7b96d84d5940084b2a37632dd65deeae662c114ceaa2c879629c9c0ad1" checksum = "10f6cb7042eda00f0049b1d2080aa4b93442997ee507eb3828e8bd7577f94c9d"
dependencies = [ dependencies = [
"futures-core", "futures-core",
"futures-task", "futures-task",
@ -638,39 +677,39 @@ dependencies = [
[[package]] [[package]]
name = "futures-io" name = "futures-io"
version = "0.3.13" version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d71c2c65c57704c32f5241c1223167c2c3294fd34ac020c807ddbe6db287ba59" checksum = "365a1a1fb30ea1c03a830fdb2158f5236833ac81fa0ad12fe35b29cddc35cb04"
[[package]] [[package]]
name = "futures-macro" name = "futures-macro"
version = "0.3.13" version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea405816a5139fb39af82c2beb921d52143f556038378d6db21183a5c37fbfb7" checksum = "668c6733a182cd7deb4f1de7ba3bf2120823835b3bcfbeacf7d2c4a773c1bb8b"
dependencies = [ dependencies = [
"proc-macro-hack", "proc-macro-hack",
"proc-macro2 1.0.24", "proc-macro2 1.0.26",
"quote 1.0.9", "quote 1.0.9",
"syn 1.0.64", "syn 1.0.69",
] ]
[[package]] [[package]]
name = "futures-sink" name = "futures-sink"
version = "0.3.13" version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85754d98985841b7d4f5e8e6fbfa4a4ac847916893ec511a2917ccd8525b8bb3" checksum = "5c5629433c555de3d82861a7a4e3794a4c40040390907cfbfd7143a92a426c23"
[[package]] [[package]]
name = "futures-task" name = "futures-task"
version = "0.3.13" version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa189ef211c15ee602667a6fcfe1c1fd9e07d42250d2156382820fba33c9df80" checksum = "ba7aa51095076f3ba6d9a1f702f74bd05ec65f555d70d2033d55ba8d69f581bc"
[[package]] [[package]]
name = "futures-util" name = "futures-util"
version = "0.3.13" version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1812c7ab8aedf8d6f2701a43e1243acdbcc2b36ab26e2ad421eb99ac963d96d1" checksum = "3c144ad54d60f23927f0a6b6d816e4271278b64f005ad65e4e35291d2de9c025"
dependencies = [ dependencies = [
"futures-channel", "futures-channel",
"futures-core", "futures-core",
@ -770,9 +809,9 @@ dependencies = [
[[package]] [[package]]
name = "handlebars" name = "handlebars"
version = "3.5.3" version = "3.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb0867bbc5a3da37a753e78021d5fcf8a4db00e18dd2dd90fd36e24190e162d" checksum = "580b6f551b29a3a02436318aed09ba1c58eea177dc49e39beac627ad356730a5"
dependencies = [ dependencies = [
"log 0.4.14", "log 0.4.14",
"pest", "pest",
@ -839,16 +878,16 @@ dependencies = [
"log 0.4.14", "log 0.4.14",
"mac", "mac",
"markup5ever", "markup5ever",
"proc-macro2 1.0.24", "proc-macro2 1.0.26",
"quote 1.0.9", "quote 1.0.9",
"syn 1.0.64", "syn 1.0.69",
] ]
[[package]] [[package]]
name = "http" name = "http"
version = "0.2.3" version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7245cd7449cc792608c3c8a9eaf69bd4eabbabf802713748fd739c98b82f0747" checksum = "527e8c9ac747e28542699a951517aa9a6945af506cd1f2e1b53a576c17b6cc11"
dependencies = [ dependencies = [
"bytes 1.0.1", "bytes 1.0.1",
"fnv", "fnv",
@ -868,9 +907,9 @@ dependencies = [
[[package]] [[package]]
name = "httparse" name = "httparse"
version = "1.3.5" version = "1.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "615caabe2c3160b313d52ccc905335f4ed5f10881dd63dc5699d47e90be85691" checksum = "bc35c995b9d93ec174cf9a27d425c7892722101e14993cd227fdb51d70cf9589"
[[package]] [[package]]
name = "httpdate" name = "httpdate"
@ -899,9 +938,9 @@ dependencies = [
[[package]] [[package]]
name = "hyper" name = "hyper"
version = "0.14.4" version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8e946c2b1349055e0b72ae281b238baf1a3ea7307c7e9f9d64673bdd9c26ac7" checksum = "8bf09f61b52cfcf4c00de50df88ae423d6c02354e385a86341133b5338630ad1"
dependencies = [ dependencies = [
"bytes 1.0.1", "bytes 1.0.1",
"futures-channel", "futures-channel",
@ -940,7 +979,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
dependencies = [ dependencies = [
"bytes 1.0.1", "bytes 1.0.1",
"hyper 0.14.4", "hyper 0.14.5",
"native-tls", "native-tls",
"tokio", "tokio",
"tokio-native-tls", "tokio-native-tls",
@ -1025,11 +1064,21 @@ version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736"
[[package]]
name = "job_scheduler"
version = "1.2.1"
source = "git+https://github.com/jjlin/job_scheduler?rev=ee023418dbba2bfe1e30a5fd7d937f9e33739806#ee023418dbba2bfe1e30a5fd7d937f9e33739806"
dependencies = [
"chrono",
"cron",
"uuid",
]
[[package]] [[package]]
name = "js-sys" name = "js-sys"
version = "0.3.49" version = "0.3.50"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc15e39392125075f60c95ba416f5381ff6c3a948ff02ab12464715adf56c821" checksum = "2d99f9e3e84b8f67f846ef5b4cbbc3b1c29f6c759fcbce6f01aa0e73d932a24c"
dependencies = [ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
@ -1088,7 +1137,7 @@ dependencies = [
"idna 0.2.2", "idna 0.2.2",
"mime 0.3.16", "mime 0.3.16",
"native-tls", "native-tls",
"nom", "nom 6.1.2",
"once_cell", "once_cell",
"quoted_printable", "quoted_printable",
"rand 0.8.3", "rand 0.8.3",
@ -1100,9 +1149,9 @@ dependencies = [
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.91" version = "0.2.93"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8916b1f6ca17130ec6568feccee27c156ad12037880833a3b842a823236502e7" checksum = "9385f66bf6105b241aa65a61cb923ef20efc665cb9f9bb50ac2f0c4b7f378d41"
[[package]] [[package]]
name = "libsqlite3-sys" name = "libsqlite3-sys"
@ -1117,9 +1166,9 @@ dependencies = [
[[package]] [[package]]
name = "lock_api" name = "lock_api"
version = "0.4.2" version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd96ffd135b2fd7b973ac026d28085defbe8983df057ced3eb4f2130b0831312" checksum = "5a3c91c24eae6777794bb1997ad98bbb87daf92890acab859f7eaa4320333176"
dependencies = [ dependencies = [
"scopeguard", "scopeguard",
] ]
@ -1223,9 +1272,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9753f12909fd8d923f75ae5c3258cae1ed3c8ec052e1b38c93c21a6d157f789c" checksum = "9753f12909fd8d923f75ae5c3258cae1ed3c8ec052e1b38c93c21a6d157f789c"
dependencies = [ dependencies = [
"migrations_internals", "migrations_internals",
"proc-macro2 1.0.24", "proc-macro2 1.0.26",
"quote 1.0.9", "quote 1.0.9",
"syn 1.0.64", "syn 1.0.69",
] ]
[[package]] [[package]]
@ -1403,6 +1452,15 @@ version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb"
[[package]]
name = "nom"
version = "4.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c349f68f25f596b9f44cf0e7c69752a5c633b0550c3ff849518bfba0233774a"
dependencies = [
"memchr",
]
[[package]] [[package]]
name = "nom" name = "nom"
version = "6.1.2" version = "6.1.2"
@ -1441,9 +1499,9 @@ version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d"
dependencies = [ dependencies = [
"proc-macro2 1.0.24", "proc-macro2 1.0.26",
"quote 1.0.9", "quote 1.0.9",
"syn 1.0.64", "syn 1.0.69",
] ]
[[package]] [[package]]
@ -1716,9 +1774,9 @@ checksum = "99b8db626e31e5b81787b9783425769681b347011cc59471e33ea46d2ea0cf55"
dependencies = [ dependencies = [
"pest", "pest",
"pest_meta", "pest_meta",
"proc-macro2 1.0.24", "proc-macro2 1.0.26",
"quote 1.0.9", "quote 1.0.9",
"syn 1.0.64", "syn 1.0.69",
] ]
[[package]] [[package]]
@ -1791,9 +1849,9 @@ version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a490329918e856ed1b083f244e3bfe2d8c4f336407e4ea9e1a9f479ff09049e5" checksum = "a490329918e856ed1b083f244e3bfe2d8c4f336407e4ea9e1a9f479ff09049e5"
dependencies = [ dependencies = [
"proc-macro2 1.0.24", "proc-macro2 1.0.26",
"quote 1.0.9", "quote 1.0.9",
"syn 1.0.64", "syn 1.0.69",
] ]
[[package]] [[package]]
@ -1858,9 +1916,9 @@ dependencies = [
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.24" version = "1.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" checksum = "a152013215dca273577e18d2bf00fa862b89b24169fb78c4c95aeb07992c9cec"
dependencies = [ dependencies = [
"unicode-xid 0.2.1", "unicode-xid 0.2.1",
] ]
@ -1892,14 +1950,14 @@ version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7"
dependencies = [ dependencies = [
"proc-macro2 1.0.24", "proc-macro2 1.0.26",
] ]
[[package]] [[package]]
name = "quoted_printable" name = "quoted_printable"
version = "0.4.2" version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47b080c5db639b292ac79cbd34be0cfc5d36694768d8341109634d90b86930e2" checksum = "1238256b09923649ec89b08104c4dfe9f6cb2fea734a5db5384e44916d59e9c5"
[[package]] [[package]]
name = "r2d2" name = "r2d2"
@ -2048,9 +2106,9 @@ dependencies = [
[[package]] [[package]]
name = "redox_syscall" name = "redox_syscall"
version = "0.2.5" version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94341e4e44e24f6b591b59e47a8a027df12e008d73fd5672dbea9cc22f4507d9" checksum = "8270314b5ccceb518e7e578952f0b72b88222d02e8f77f5ecf7abbb673539041"
dependencies = [ dependencies = [
"bitflags", "bitflags",
] ]
@ -2083,10 +2141,11 @@ dependencies = [
[[package]] [[package]]
name = "reqwest" name = "reqwest"
version = "0.11.2" version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf12057f289428dbf5c591c74bf10392e4a8003f993405a902f20117019022d4" checksum = "2296f2fac53979e8ccbc4a1136b25dcefd37be9ed7e4a1f6b05a6029c84ff124"
dependencies = [ dependencies = [
"async-compression",
"base64 0.13.0", "base64 0.13.0",
"bytes 1.0.1", "bytes 1.0.1",
"encoding_rs", "encoding_rs",
@ -2094,7 +2153,7 @@ dependencies = [
"futures-util", "futures-util",
"http", "http",
"http-body", "http-body",
"hyper 0.14.4", "hyper 0.14.5",
"hyper-tls", "hyper-tls",
"ipnet", "ipnet",
"js-sys", "js-sys",
@ -2109,6 +2168,8 @@ dependencies = [
"serde_urlencoded", "serde_urlencoded",
"tokio", "tokio",
"tokio-native-tls", "tokio-native-tls",
"tokio-socks",
"tokio-util",
"url 2.2.1", "url 2.2.1",
"wasm-bindgen", "wasm-bindgen",
"wasm-bindgen-futures", "wasm-bindgen-futures",
@ -2296,9 +2357,9 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]] [[package]]
name = "sct" name = "sct"
version = "0.6.0" version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3042af939fca8c3453b7af0f1c66e533a15a86169e39de2657310ade8f98d3c" checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce"
dependencies = [ dependencies = [
"ring", "ring",
"untrusted", "untrusted",
@ -2306,9 +2367,9 @@ dependencies = [
[[package]] [[package]]
name = "security-framework" name = "security-framework"
version = "2.1.2" version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d493c5f39e02dfb062cd8f33301f90f9b13b650e8c1b1d0fd75c19dd64bff69d" checksum = "3670b1d2fdf6084d192bc71ead7aabe6c06aa2ea3fbd9cc3ac111fa5c2b1bd84"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"core-foundation", "core-foundation",
@ -2319,9 +2380,9 @@ dependencies = [
[[package]] [[package]]
name = "security-framework-sys" name = "security-framework-sys"
version = "2.1.1" version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dee48cdde5ed250b0d3252818f646e174ab414036edb884dde62d80a3ac6082d" checksum = "3676258fd3cfe2c9a0ec99ce3038798d847ce3e4bb17746373eb9f0f1ac16339"
dependencies = [ dependencies = [
"core-foundation-sys", "core-foundation-sys",
"libc", "libc",
@ -2357,9 +2418,9 @@ version = "1.0.125"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b093b7a2bb58203b5da3056c05b4ec1fed827dcfdb37347a8841695263b3d06d" checksum = "b093b7a2bb58203b5da3056c05b4ec1fed827dcfdb37347a8841695263b3d06d"
dependencies = [ dependencies = [
"proc-macro2 1.0.24", "proc-macro2 1.0.26",
"quote 1.0.9", "quote 1.0.9",
"syn 1.0.64", "syn 1.0.69",
] ]
[[package]] [[package]]
@ -2482,11 +2543,10 @@ checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e"
[[package]] [[package]]
name = "socket2" name = "socket2"
version = "0.3.19" version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "122e570113d28d773067fab24266b66753f6ea915758651696b6e35e49f88d6e" checksum = "9e3dfc207c526015c632472a77be09cf1b6e46866581aecae5cc38fb4235dea2"
dependencies = [ dependencies = [
"cfg-if 1.0.0",
"libc", "libc",
"winapi 0.3.9", "winapi 0.3.9",
] ]
@ -2538,11 +2598,11 @@ version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef" checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef"
dependencies = [ dependencies = [
"proc-macro2 1.0.24", "proc-macro2 1.0.26",
"quote 1.0.9", "quote 1.0.9",
"serde", "serde",
"serde_derive", "serde_derive",
"syn 1.0.64", "syn 1.0.69",
] ]
[[package]] [[package]]
@ -2552,13 +2612,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11" checksum = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11"
dependencies = [ dependencies = [
"base-x", "base-x",
"proc-macro2 1.0.24", "proc-macro2 1.0.26",
"quote 1.0.9", "quote 1.0.9",
"serde", "serde",
"serde_derive", "serde_derive",
"serde_json", "serde_json",
"sha1", "sha1",
"syn 1.0.64", "syn 1.0.69",
] ]
[[package]] [[package]]
@ -2588,7 +2648,7 @@ checksum = "f24c8e5e19d22a726626f1a5e16fe15b132dcf21d10177fa5a45ce7962996b97"
dependencies = [ dependencies = [
"phf_generator", "phf_generator",
"phf_shared", "phf_shared",
"proc-macro2 1.0.24", "proc-macro2 1.0.26",
"quote 1.0.9", "quote 1.0.9",
] ]
@ -2611,11 +2671,11 @@ dependencies = [
[[package]] [[package]]
name = "syn" name = "syn"
version = "1.0.64" version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fd9d1e9976102a03c542daa2eff1b43f9d72306342f3f8b3ed5fb8908195d6f" checksum = "48fe99c6bd8b1cc636890bcc071842de909d902c81ac7dab53ba33c421ab8ffb"
dependencies = [ dependencies = [
"proc-macro2 1.0.24", "proc-macro2 1.0.26",
"quote 1.0.9", "quote 1.0.9",
"unicode-xid 0.2.1", "unicode-xid 0.2.1",
] ]
@ -2663,6 +2723,26 @@ dependencies = [
"utf-8", "utf-8",
] ]
[[package]]
name = "thiserror"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0f4a65597094d4483ddaed134f409b2cb7c1beccf25201a9f73c719254fa98e"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7765189610d8241a44529806d6fd1f2e0a08734313a35d5b3a556f92b381f3c0"
dependencies = [
"proc-macro2 1.0.26",
"quote 1.0.9",
"syn 1.0.69",
]
[[package]] [[package]]
name = "threadpool" name = "threadpool"
version = "1.8.1" version = "1.8.1"
@ -2715,17 +2795,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5c3be1edfad6027c69f5491cf4cb310d1a71ecd6af742788c6ff8bced86b8fa" checksum = "e5c3be1edfad6027c69f5491cf4cb310d1a71ecd6af742788c6ff8bced86b8fa"
dependencies = [ dependencies = [
"proc-macro-hack", "proc-macro-hack",
"proc-macro2 1.0.24", "proc-macro2 1.0.26",
"quote 1.0.9", "quote 1.0.9",
"standback", "standback",
"syn 1.0.64", "syn 1.0.69",
] ]
[[package]] [[package]]
name = "tinyvec" name = "tinyvec"
version = "1.1.1" version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "317cca572a0e89c3ce0ca1f1bdc9369547fe318a683418e42ac8f59d14701023" checksum = "5b5220f05bb7de7f3f53c7c065e1199b3172696fe2db9f9c4d8ad9b4ee74c342"
dependencies = [ dependencies = [
"tinyvec_macros", "tinyvec_macros",
] ]
@ -2738,9 +2818,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.4.0" version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "134af885d758d645f0f0505c9a8b3f9bf8a348fd822e112ab5248138348f1722" checksum = "83f0c8e7c0addab50b663055baf787d0af7f413a46e6e7fb9559a4e4db7137a5"
dependencies = [ dependencies = [
"autocfg", "autocfg",
"bytes 1.0.1", "bytes 1.0.1",
@ -2761,11 +2841,23 @@ dependencies = [
"tokio", "tokio",
] ]
[[package]]
name = "tokio-socks"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51165dfa029d2a65969413a6cc96f354b86b464498702f174a4efa13608fd8c0"
dependencies = [
"either",
"futures-util",
"thiserror",
"tokio",
]
[[package]] [[package]]
name = "tokio-util" name = "tokio-util"
version = "0.6.5" version = "0.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5143d049e85af7fbc36f5454d990e62c2df705b3589f123b71f441b6b59f443f" checksum = "940a12c99365c31ea8dd9ba04ec1be183ffe4920102bb7122c2f515437601e8e"
dependencies = [ dependencies = [
"bytes 1.0.1", "bytes 1.0.1",
"futures-core", "futures-core",
@ -2797,10 +2889,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01ebdc2bb4498ab1ab5f5b73c5803825e60199229ccba0698170e3be0e7f959f" checksum = "01ebdc2bb4498ab1ab5f5b73c5803825e60199229ccba0698170e3be0e7f959f"
dependencies = [ dependencies = [
"cfg-if 1.0.0", "cfg-if 1.0.0",
"log 0.4.14",
"pin-project-lite", "pin-project-lite",
"tracing-attributes",
"tracing-core", "tracing-core",
] ]
[[package]]
name = "tracing-attributes"
version = "0.1.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c42e6fa53307c8a17e4ccd4dc81cf5ec38db9209f59b222210375b54ee40d1e2"
dependencies = [
"proc-macro2 1.0.26",
"quote 1.0.9",
"syn 1.0.69",
]
[[package]] [[package]]
name = "tracing-core" name = "tracing-core"
version = "0.1.17" version = "0.1.17"
@ -2886,9 +2991,9 @@ dependencies = [
[[package]] [[package]]
name = "unicode-bidi" name = "unicode-bidi"
version = "0.3.4" version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" checksum = "eeb8be209bb1c96b7c177c7420d26e04eccacb0eeae6b980e35fcb74678107e0"
dependencies = [ dependencies = [
"matches", "matches",
] ]
@ -2958,6 +3063,57 @@ dependencies = [
"getrandom 0.2.2", "getrandom 0.2.2",
] ]
[[package]]
name = "vaultwarden"
version = "1.0.0"
dependencies = [
"backtrace",
"chashmap",
"chrono",
"chrono-tz",
"data-encoding",
"data-url",
"diesel",
"diesel_migrations",
"dotenv",
"fern",
"handlebars",
"html5ever",
"idna 0.2.2",
"job_scheduler",
"jsonwebtoken",
"lettre",
"libsqlite3-sys",
"log 0.4.14",
"markup5ever_rcdom",
"multipart",
"newline-converter",
"num-derive",
"num-traits",
"oath",
"once_cell",
"openssl",
"parity-ws",
"paste",
"percent-encoding 2.1.0",
"pico-args",
"rand 0.8.3",
"regex",
"reqwest",
"ring",
"rmpv",
"rocket",
"rocket_contrib",
"serde",
"serde_json",
"syslog",
"time 0.2.26",
"tracing",
"u2f",
"uuid",
"yubico",
]
[[package]] [[package]]
name = "vcpkg" name = "vcpkg"
version = "0.2.11" version = "0.2.11"
@ -3011,9 +3167,9 @@ checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
[[package]] [[package]]
name = "wasm-bindgen" name = "wasm-bindgen"
version = "0.2.72" version = "0.2.73"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fe8f61dba8e5d645a4d8132dc7a0a66861ed5e1045d2c0ed940fab33bac0fbe" checksum = "83240549659d187488f91f33c0f8547cbfef0b2088bc470c116d1d260ef623d9"
dependencies = [ dependencies = [
"cfg-if 1.0.0", "cfg-if 1.0.0",
"serde", "serde",
@ -3023,24 +3179,24 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-backend" name = "wasm-bindgen-backend"
version = "0.2.72" version = "0.2.73"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "046ceba58ff062da072c7cb4ba5b22a37f00a302483f7e2a6cdc18fedbdc1fd3" checksum = "ae70622411ca953215ca6d06d3ebeb1e915f0f6613e3b495122878d7ebec7dae"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"lazy_static", "lazy_static",
"log 0.4.14", "log 0.4.14",
"proc-macro2 1.0.24", "proc-macro2 1.0.26",
"quote 1.0.9", "quote 1.0.9",
"syn 1.0.64", "syn 1.0.69",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
[[package]] [[package]]
name = "wasm-bindgen-futures" name = "wasm-bindgen-futures"
version = "0.4.22" version = "0.4.23"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73157efb9af26fb564bb59a009afd1c7c334a44db171d280690d0c3faaec3468" checksum = "81b8b767af23de6ac18bf2168b690bed2902743ddf0fb39252e36f9e2bfc63ea"
dependencies = [ dependencies = [
"cfg-if 1.0.0", "cfg-if 1.0.0",
"js-sys", "js-sys",
@ -3050,9 +3206,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-macro" name = "wasm-bindgen-macro"
version = "0.2.72" version = "0.2.73"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ef9aa01d36cda046f797c57959ff5f3c615c9cc63997a8d545831ec7976819b" checksum = "3e734d91443f177bfdb41969de821e15c516931c3c3db3d318fa1b68975d0f6f"
dependencies = [ dependencies = [
"quote 1.0.9", "quote 1.0.9",
"wasm-bindgen-macro-support", "wasm-bindgen-macro-support",
@ -3060,28 +3216,28 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-macro-support" name = "wasm-bindgen-macro-support"
version = "0.2.72" version = "0.2.73"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96eb45c1b2ee33545a813a92dbb53856418bf7eb54ab34f7f7ff1448a5b3735d" checksum = "d53739ff08c8a68b0fdbcd54c372b8ab800b1449ab3c9d706503bc7dd1621b2c"
dependencies = [ dependencies = [
"proc-macro2 1.0.24", "proc-macro2 1.0.26",
"quote 1.0.9", "quote 1.0.9",
"syn 1.0.64", "syn 1.0.69",
"wasm-bindgen-backend", "wasm-bindgen-backend",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
[[package]] [[package]]
name = "wasm-bindgen-shared" name = "wasm-bindgen-shared"
version = "0.2.72" version = "0.2.73"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7148f4696fb4960a346eaa60bbfb42a1ac4ebba21f750f75fc1375b098d5ffa" checksum = "d9a543ae66aa233d14bb765ed9af4a33e81b8b58d1584cf1b47ff8cd0b9e4489"
[[package]] [[package]]
name = "web-sys" name = "web-sys"
version = "0.3.49" version = "0.3.50"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59fe19d70f5dacc03f6e46777213facae5ac3801575d56ca6cbd4c93dcd12310" checksum = "a905d57e488fec8861446d3393670fb50d27a262344013181c2cdf9fff5481be"
dependencies = [ dependencies = [
"js-sys", "js-sys",
"wasm-bindgen", "wasm-bindgen",

@ -1,10 +1,10 @@
[package] [package]
name = "bitwarden_rs" name = "vaultwarden"
version = "1.0.0" version = "1.0.0"
authors = ["Daniel García <dani-garcia@users.noreply.github.com>"] authors = ["Daniel García <dani-garcia@users.noreply.github.com>"]
edition = "2018" edition = "2018"
repository = "https://github.com/dani-garcia/bitwarden_rs" repository = "https://github.com/dani-garcia/vaultwarden"
readme = "README.md" readme = "README.md"
license = "GPL-3.0-only" license = "GPL-3.0-only"
publish = false publish = false
@ -32,7 +32,7 @@ rocket = { version = "0.5.0-dev", features = ["tls"], default-features = false }
rocket_contrib = "0.5.0-dev" rocket_contrib = "0.5.0-dev"
# HTTP client # HTTP client
reqwest = { version = "0.11.2", features = ["blocking", "json"] } reqwest = { version = "0.11.3", features = ["blocking", "json", "gzip", "brotli", "socks"] }
# multipart/form-data support # multipart/form-data support
multipart = { version = "0.17.1", features = ["server"], default-features = false } multipart = { version = "0.17.1", features = ["server"], default-features = false }
@ -73,6 +73,9 @@ chrono = { version = "0.4.19", features = ["serde"] }
chrono-tz = "0.5.3" chrono-tz = "0.5.3"
time = "0.2.26" time = "0.2.26"
# Job scheduler
job_scheduler = "1.2.1"
# TOTP library # TOTP library
oath = "0.10.2" oath = "0.10.2"
@ -99,11 +102,12 @@ num-traits = "0.2.14"
num-derive = "0.3.3" num-derive = "0.3.3"
# Email libraries # Email libraries
tracing = { version = "0.1.25", features = ["log"] } # Needed to have lettre trace logging used when SMTP_DEBUG is enabled.
lettre = { version = "0.10.0-beta.3", features = ["smtp-transport", "builder", "serde", "native-tls", "hostname", "tracing"], default-features = false } lettre = { version = "0.10.0-beta.3", features = ["smtp-transport", "builder", "serde", "native-tls", "hostname", "tracing"], default-features = false }
newline-converter = "0.2.0" newline-converter = "0.2.0"
# Template library # Template library
handlebars = { version = "3.5.3", features = ["dir_source"] } handlebars = { version = "3.5.4", features = ["dir_source"] }
# For favicon extraction from main website # For favicon extraction from main website
html5ever = "0.25.1" html5ever = "0.25.1"
@ -135,3 +139,10 @@ rocket_contrib = { git = 'https://github.com/SergioBenitez/Rocket', rev = '263e3
# For favicon extraction from main website # For favicon extraction from main website
data-url = { git = 'https://github.com/servo/rust-url', package="data-url", rev = '540ede02d0771824c0c80ff9f57fe8eff38b1291' } data-url = { git = 'https://github.com/servo/rust-url', package="data-url", rev = '540ede02d0771824c0c80ff9f57fe8eff38b1291' }
# The maintainer of the `job_scheduler` crate doesn't seem to have responded
# to any issues or PRs for almost a year (as of April 2021). This hopefully
# temporary fork updates Cargo.toml to use more up-to-date dependencies.
# In particular, `cron` has since implemented parsing of some common syntax
# that wasn't previously supported (https://github.com/zslayton/cron/pull/64).
job_scheduler = { git = 'https://github.com/jjlin/job_scheduler', rev = 'ee023418dbba2bfe1e30a5fd7d937f9e33739806' }

@ -1,15 +1,14 @@
### This is a Bitwarden server API implementation written in Rust compatible with [upstream Bitwarden clients](https://bitwarden.com/#download)*, perfect for self-hosted deployment where running the official resource-heavy service might not be ideal. ### Alternative implementation of the Bitwarden server API written in Rust and compatible with [upstream Bitwarden clients](https://bitwarden.com/#download)*, perfect for self-hosted deployment where running the official resource-heavy service might not be ideal.
--- ---
[![Travis Build Status](https://travis-ci.org/dani-garcia/bitwarden_rs.svg?branch=master)](https://travis-ci.org/dani-garcia/bitwarden_rs) [![Docker Pulls](https://img.shields.io/docker/pulls/bitwardenrs/server.svg)](https://hub.docker.com/r/vaultwarden/server)
[![Docker Pulls](https://img.shields.io/docker/pulls/bitwardenrs/server.svg)](https://hub.docker.com/r/bitwardenrs/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/bitwarden_rs/status.svg)](https://deps.rs/repo/github/dani-garcia/bitwarden_rs) [![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/bitwarden_rs.svg)](https://github.com/dani-garcia/bitwarden_rs/releases/latest) [![GPL-3.0 Licensed](https://img.shields.io/github/license/dani-garcia/vaultwarden.svg)](https://github.com/dani-garcia/vaultwarden/blob/master/LICENSE.txt)
[![GPL-3.0 Licensed](https://img.shields.io/github/license/dani-garcia/bitwarden_rs.svg)](https://github.com/dani-garcia/bitwarden_rs/blob/master/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/bitwarden_rs:matrix.org.svg?logo=matrix)](https://matrix.to/#/#bitwarden_rs:matrix.org)
Image is based on [Rust implementation of Bitwarden API](https://github.com/dani-garcia/bitwarden_rs). Image is based on [Rust implementation of Bitwarden API](https://github.com/dani-garcia/vaultwarden).
**This project is not associated with the [Bitwarden](https://bitwarden.com/) project nor 8bit Solutions LLC.** **This project is not associated with the [Bitwarden](https://bitwarden.com/) project nor 8bit Solutions LLC.**
@ -33,29 +32,57 @@ Basically full implementation of Bitwarden API is provided including:
Pull the docker image and mount a volume from the host for persistent storage: Pull the docker image and mount a volume from the host for persistent storage:
```sh ```sh
docker pull bitwardenrs/server:latest docker pull vaultwarden/server:latest
docker run -d --name bitwarden -v /bw-data/:/data/ -p 80:80 bitwardenrs/server:latest docker run -d --name vaultwarden -v /vw-data/:/data/ -p 80:80 vaultwarden/server:latest
``` ```
This will preserve any persistent data under /bw-data/, you can adapt the path to whatever suits you. This will preserve any persistent data under /bw-data/, you can adapt the path to whatever suits you.
**IMPORTANT**: Some web browsers, like Chrome, disallow the use of Web Crypto APIs in insecure contexts. In this case, you might get an error like `Cannot read property 'importKey'`. To solve this problem, you need to access the web vault from HTTPS. **IMPORTANT**: Some web browsers, like Chrome, disallow the use of Web Crypto APIs in insecure contexts. In this case, you might get an error like `Cannot read property 'importKey'`. To solve this problem, you need to access the web vault from HTTPS.
This can be configured in [bitwarden_rs directly](https://github.com/dani-garcia/bitwarden_rs/wiki/Enabling-HTTPS) or using a third-party reverse proxy ([some examples](https://github.com/dani-garcia/bitwarden_rs/wiki/Proxy-examples)). This can be configured in [vaultwarden directly](https://github.com/dani-garcia/vaultwarden/wiki/Enabling-HTTPS) or using a third-party reverse proxy ([some examples](https://github.com/dani-garcia/vaultwarden/wiki/Proxy-examples)).
If you have an available domain name, you can get HTTPS certificates with [Let's Encrypt](https://letsencrypt.org/), or you can generate self-signed certificates with utilities like [mkcert](https://github.com/FiloSottile/mkcert). Some proxies automatically do this step, like Caddy (see examples linked above). If you have an available domain name, you can get HTTPS certificates with [Let's Encrypt](https://letsencrypt.org/), or you can generate self-signed certificates with utilities like [mkcert](https://github.com/FiloSottile/mkcert). Some proxies automatically do this step, like Caddy (see examples linked above).
## Usage ## Usage
See the [bitwarden_rs wiki](https://github.com/dani-garcia/bitwarden_rs/wiki) for more information on how to configure and run the bitwarden_rs server. See the [vaultwarden wiki](https://github.com/dani-garcia/vaultwarden/wiki) for more information on how to configure and run the vaultwarden server.
## Get in touch ## Get in touch
To ask a question, offer suggestions or new features or to get help configuring or installing the software, please [use the forum](https://bitwardenrs.discourse.group/). To ask a question, offer suggestions or new features or to get help configuring or installing the software, please [use the forum](https://vaultwarden.discourse.group/).
If you spot any bugs or crashes with bitwarden_rs itself, please [create an issue](https://github.com/dani-garcia/bitwarden_rs/issues/). Make sure there aren't any similar issues open, though! If you spot any bugs or crashes with vaultwarden itself, please [create an issue](https://github.com/dani-garcia/vaultwarden/issues/). Make sure there aren't any similar issues open, though!
If you prefer to chat, we're usually hanging around at [#bitwarden_rs:matrix.org](https://matrix.to/#/#bitwarden_rs:matrix.org) room on Matrix. Feel free to join us! If you prefer to chat, we're usually hanging around at [#vaultwarden:matrix.org](https://matrix.to/#/#vaultwarden:matrix.org) room on Matrix. Feel free to join us!
### Sponsors ### Sponsors
Thanks for your contribution to the project! Thanks for your contribution to the project!
- [@ChonoN](https://github.com/ChonoN) <table>
- [@themightychris](https://github.com/themightychris) <tr>
<td align="center">
<a href="https://github.com/netdadaltd">
<img src="https://avatars.githubusercontent.com/u/77323954?s=75&v=4" width="75px;" alt="netdadaltd"/>
<br />
<sub><b>netDada Ltd.</b></sub>
</a>
</td>
</tr>
</table>
<br/>
<table>
<tr>
<td align="center">
<a href="https://github.com/ChonoN" style="width: 75px">
<sub><b>ChonoN</b></sub>
</a>
</td>
</tr>
<tr>
<td align="center">
<a href="https://github.com/themightychris">
<sub><b>themightychris</b></sub>
</a>
</td>
</tr>
</table>

@ -1,5 +1,5 @@
use std::process::Command;
use std::env; use std::env;
use std::process::Command;
fn main() { fn main() {
// This allow using #[cfg(sqlite)] instead of #[cfg(feature = "sqlite")], which helps when trying to add them through macros // This allow using #[cfg(sqlite)] instead of #[cfg(feature = "sqlite")], which helps when trying to add them through macros
@ -11,7 +11,9 @@ fn main() {
println!("cargo:rustc-cfg=postgresql"); println!("cargo:rustc-cfg=postgresql");
#[cfg(not(any(feature = "sqlite", feature = "mysql", feature = "postgresql")))] #[cfg(not(any(feature = "sqlite", feature = "mysql", feature = "postgresql")))]
compile_error!("You need to enable one DB backend. To build with previous defaults do: cargo build --features sqlite"); compile_error!(
"You need to enable one DB backend. To build with previous defaults do: cargo build --features sqlite"
);
if let Ok(version) = env::var("BWRS_VERSION") { if let Ok(version) = env::var("BWRS_VERSION") {
println!("cargo:rustc-env=BWRS_VERSION={}", version); println!("cargo:rustc-env=BWRS_VERSION={}", version);
@ -56,7 +58,7 @@ fn read_git_info() -> Result<(), std::io::Error> {
// Combined version // Combined version
let version = if let Some(exact) = exact_tag { let version = if let Some(exact) = exact_tag {
exact exact
} else if &branch != "master" { } else if &branch != "main" && &branch != "master" {
format!("{}-{} ({})", last_tag, rev_short, branch) format!("{}-{} ({})", last_tag, rev_short, branch)
} else { } else {
format!("{}-{}", last_tag, rev_short) format!("{}-{}", last_tag, rev_short)

@ -1,7 +1,7 @@
# The cross-built images have the build arch (`amd64`) embedded in the image # The cross-built images have the build arch (`amd64`) embedded in the image
# manifest, rather than the target arch. For example: # manifest, rather than the target arch. For example:
# #
# $ docker inspect bitwardenrs/server:latest-armv7 | jq -r '.[]|.Architecture' # $ docker inspect vaultwarden/server:latest-armv7 | jq -r '.[]|.Architecture'
# amd64 # amd64
# #
# Recent versions of Docker have started printing a warning when the image's # Recent versions of Docker have started printing a warning when the image's

@ -1,10 +1,10 @@
# This file was generated using a Jinja2 template. # This file was generated using a Jinja2 template.
# Please make your changes in `Dockerfile.j2` and then `make` the individual Dockerfiles. # Please make your changes in `Dockerfile.j2` and then `make` the individual Dockerfiles.
{% set build_stage_base_image = "rust:1.50" %} {% set build_stage_base_image = "rust:1.51" %}
{% if "alpine" in target_file %} {% if "alpine" in target_file %}
{% if "amd64" in target_file %} {% if "amd64" in target_file %}
{% set build_stage_base_image = "clux/muslrust:nightly-2021-02-22" %} {% set build_stage_base_image = "clux/muslrust:nightly-2021-04-14" %}
{% set runtime_stage_base_image = "alpine:3.13" %} {% set runtime_stage_base_image = "alpine:3.13" %}
{% set package_arch_target = "x86_64-unknown-linux-musl" %} {% set package_arch_target = "x86_64-unknown-linux-musl" %}
{% elif "armv7" in target_file %} {% elif "armv7" in target_file %}
@ -44,26 +44,26 @@
# https://docs.docker.com/develop/develop-images/multistage-build/ # https://docs.docker.com/develop/develop-images/multistage-build/
# https://whitfin.io/speeding-up-rust-docker-builds/ # https://whitfin.io/speeding-up-rust-docker-builds/
####################### VAULT BUILD IMAGE ####################### ####################### VAULT BUILD IMAGE #######################
{% set vault_version = "2.19.0" %} {% set vault_version = "2.19.0d" %}
{% set vault_image_digest = "sha256:8747cfaa2c6d87d1749e119dd884697e8099389aa9aca30a4d73d4ff796fe0e4" %} {% set vault_image_digest = "sha256:a7bd6bc4db33bd45f723c4b1ac90918b7f80204560683cfc8efd9efd03a9b233" %}
# The web-vault digest specifies a particular web-vault build on Docker Hub. # The web-vault digest specifies a particular web-vault build on Docker Hub.
# Using the digest instead of the tag name provides better security, # Using the digest instead of the tag name provides better security,
# as the digest of an image is immutable, whereas a tag name can later # as the digest of an image is immutable, whereas a tag name can later
# be changed to point to a malicious image. # be changed to point to a malicious image.
# #
# To verify the current digest for a given tag name: # To verify the current digest for a given tag name:
# - From https://hub.docker.com/r/bitwardenrs/web-vault/tags, # - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
# click the tag name to view the digest of the image it currently points to. # click the tag name to view the digest of the image it currently points to.
# - From the command line: # - From the command line:
# $ docker pull bitwardenrs/web-vault:v{{ vault_version }} # $ docker pull vaultwarden/web-vault:v{{ vault_version }}
# $ docker image inspect --format "{{ '{{' }}.RepoDigests}}" bitwardenrs/web-vault:v{{ vault_version }} # $ docker image inspect --format "{{ '{{' }}.RepoDigests}}" vaultwarden/web-vault:v{{ vault_version }}
# [bitwardenrs/web-vault@{{ vault_image_digest }}] # [vaultwarden/web-vault@{{ vault_image_digest }}]
# #
# - Conversely, to get the tag name from the digest: # - Conversely, to get the tag name from the digest:
# $ docker image inspect --format "{{ '{{' }}.RepoTags}}" bitwardenrs/web-vault@{{ vault_image_digest }} # $ docker image inspect --format "{{ '{{' }}.RepoTags}}" vaultwarden/web-vault@{{ vault_image_digest }}
# [bitwardenrs/web-vault:v{{ vault_version }}] # [vaultwarden/web-vault:v{{ vault_version }}]
# #
FROM bitwardenrs/web-vault@{{ vault_image_digest }} as vault 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
@ -189,7 +189,7 @@ RUN touch src/main.rs
RUN cargo build --features ${DB} --release{{ package_arch_target_param }} RUN cargo build --features ${DB} --release{{ package_arch_target_param }}
{% if "alpine" in target_file %} {% if "alpine" in target_file %}
{% if "armv7" in target_file %} {% if "armv7" in target_file %}
RUN musl-strip target/{{ package_arch_target }}/release/bitwarden_rs RUN musl-strip target/{{ package_arch_target }}/release/vaultwarden
{% endif %} {% endif %}
{% endif %} {% endif %}
@ -215,9 +215,6 @@ RUN apk add --no-cache \
openssl \ openssl \
curl \ curl \
dumb-init \ dumb-init \
{% if "sqlite" in features %}
sqlite \
{% endif %}
{% if "mysql" in features %} {% if "mysql" in features %}
mariadb-connector-c \ mariadb-connector-c \
{% endif %} {% endif %}
@ -232,7 +229,6 @@ RUN apt-get update && apt-get install -y \
ca-certificates \ ca-certificates \
curl \ curl \
dumb-init \ dumb-init \
sqlite3 \
libmariadb-dev-compat \ libmariadb-dev-compat \
libpq5 \ libpq5 \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
@ -250,12 +246,13 @@ EXPOSE 3012
# Copies the files from the context (Rocket.toml file and web-vault) # Copies the files from the context (Rocket.toml file and web-vault)
# and the binary from the "build" stage to the current stage # and the binary from the "build" stage to the current stage
WORKDIR /
COPY Rocket.toml . COPY Rocket.toml .
COPY --from=vault /web-vault ./web-vault COPY --from=vault /web-vault ./web-vault
{% if package_arch_target is defined %} {% if package_arch_target is defined %}
COPY --from=build /app/target/{{ package_arch_target }}/release/bitwarden_rs . COPY --from=build /app/target/{{ package_arch_target }}/release/vaultwarden .
{% else %} {% else %}
COPY --from=build /app/target/release/bitwarden_rs . COPY --from=build /app/target/release/vaultwarden .
{% endif %} {% endif %}
COPY docker/healthcheck.sh /healthcheck.sh COPY docker/healthcheck.sh /healthcheck.sh
@ -264,6 +261,5 @@ COPY docker/start.sh /start.sh
HEALTHCHECK --interval=60s --timeout=10s CMD ["/healthcheck.sh"] HEALTHCHECK --interval=60s --timeout=10s CMD ["/healthcheck.sh"]
# Configures the startup! # Configures the startup!
WORKDIR /
ENTRYPOINT ["/usr/bin/dumb-init", "--"] ENTRYPOINT ["/usr/bin/dumb-init", "--"]
CMD ["/start.sh"] CMD ["/start.sh"]

@ -11,21 +11,21 @@
# be changed to point to a malicious image. # be changed to point to a malicious image.
# #
# To verify the current digest for a given tag name: # To verify the current digest for a given tag name:
# - From https://hub.docker.com/r/bitwardenrs/web-vault/tags, # - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
# click the tag name to view the digest of the image it currently points to. # click the tag name to view the digest of the image it currently points to.
# - From the command line: # - From the command line:
# $ docker pull bitwardenrs/web-vault:v2.19.0 # $ docker pull vaultwarden/web-vault:v2.19.0d
# $ docker image inspect --format "{{.RepoDigests}}" bitwardenrs/web-vault:v2.19.0 # $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2.19.0d
# [bitwardenrs/web-vault@sha256:8747cfaa2c6d87d1749e119dd884697e8099389aa9aca30a4d73d4ff796fe0e4] # [vaultwarden/web-vault@sha256:a7bd6bc4db33bd45f723c4b1ac90918b7f80204560683cfc8efd9efd03a9b233]
# #
# - Conversely, to get the tag name from the digest: # - Conversely, to get the tag name from the digest:
# $ docker image inspect --format "{{.RepoTags}}" bitwardenrs/web-vault@sha256:8747cfaa2c6d87d1749e119dd884697e8099389aa9aca30a4d73d4ff796fe0e4 # $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:a7bd6bc4db33bd45f723c4b1ac90918b7f80204560683cfc8efd9efd03a9b233
# [bitwardenrs/web-vault:v2.19.0] # [vaultwarden/web-vault:v2.19.0d]
# #
FROM bitwardenrs/web-vault@sha256:8747cfaa2c6d87d1749e119dd884697e8099389aa9aca30a4d73d4ff796fe0e4 as vault FROM vaultwarden/web-vault@sha256:a7bd6bc4db33bd45f723c4b1ac90918b7f80204560683cfc8efd9efd03a9b233 as vault
########################## BUILD IMAGE ########################## ########################## BUILD IMAGE ##########################
FROM rust:1.50 as build FROM rust:1.51 as build
# Debian-based builds support multidb # Debian-based builds support multidb
ARG DB=sqlite,mysql,postgresql ARG DB=sqlite,mysql,postgresql
@ -86,7 +86,6 @@ RUN apt-get update && apt-get install -y \
ca-certificates \ ca-certificates \
curl \ curl \
dumb-init \ dumb-init \
sqlite3 \
libmariadb-dev-compat \ libmariadb-dev-compat \
libpq5 \ libpq5 \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
@ -98,9 +97,10 @@ EXPOSE 3012
# Copies the files from the context (Rocket.toml file and web-vault) # Copies the files from the context (Rocket.toml file and web-vault)
# and the binary from the "build" stage to the current stage # and the binary from the "build" stage to the current stage
WORKDIR /
COPY Rocket.toml . COPY Rocket.toml .
COPY --from=vault /web-vault ./web-vault COPY --from=vault /web-vault ./web-vault
COPY --from=build /app/target/release/bitwarden_rs . COPY --from=build /app/target/release/vaultwarden .
COPY docker/healthcheck.sh /healthcheck.sh COPY docker/healthcheck.sh /healthcheck.sh
COPY docker/start.sh /start.sh COPY docker/start.sh /start.sh
@ -108,6 +108,5 @@ COPY docker/start.sh /start.sh
HEALTHCHECK --interval=60s --timeout=10s CMD ["/healthcheck.sh"] HEALTHCHECK --interval=60s --timeout=10s CMD ["/healthcheck.sh"]
# Configures the startup! # Configures the startup!
WORKDIR /
ENTRYPOINT ["/usr/bin/dumb-init", "--"] ENTRYPOINT ["/usr/bin/dumb-init", "--"]
CMD ["/start.sh"] CMD ["/start.sh"]

@ -11,21 +11,21 @@
# be changed to point to a malicious image. # be changed to point to a malicious image.
# #
# To verify the current digest for a given tag name: # To verify the current digest for a given tag name:
# - From https://hub.docker.com/r/bitwardenrs/web-vault/tags, # - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
# click the tag name to view the digest of the image it currently points to. # click the tag name to view the digest of the image it currently points to.
# - From the command line: # - From the command line:
# $ docker pull bitwardenrs/web-vault:v2.19.0 # $ docker pull vaultwarden/web-vault:v2.19.0d
# $ docker image inspect --format "{{.RepoDigests}}" bitwardenrs/web-vault:v2.19.0 # $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2.19.0d
# [bitwardenrs/web-vault@sha256:8747cfaa2c6d87d1749e119dd884697e8099389aa9aca30a4d73d4ff796fe0e4] # [vaultwarden/web-vault@sha256:a7bd6bc4db33bd45f723c4b1ac90918b7f80204560683cfc8efd9efd03a9b233]
# #
# - Conversely, to get the tag name from the digest: # - Conversely, to get the tag name from the digest:
# $ docker image inspect --format "{{.RepoTags}}" bitwardenrs/web-vault@sha256:8747cfaa2c6d87d1749e119dd884697e8099389aa9aca30a4d73d4ff796fe0e4 # $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:a7bd6bc4db33bd45f723c4b1ac90918b7f80204560683cfc8efd9efd03a9b233
# [bitwardenrs/web-vault:v2.19.0] # [vaultwarden/web-vault:v2.19.0d]
# #
FROM bitwardenrs/web-vault@sha256:8747cfaa2c6d87d1749e119dd884697e8099389aa9aca30a4d73d4ff796fe0e4 as vault FROM vaultwarden/web-vault@sha256:a7bd6bc4db33bd45f723c4b1ac90918b7f80204560683cfc8efd9efd03a9b233 as vault
########################## BUILD IMAGE ########################## ########################## BUILD IMAGE ##########################
FROM clux/muslrust:nightly-2021-02-22 as build FROM clux/muslrust:nightly-2021-04-14 as build
# Alpine-based AMD64 (musl) does not support mysql/mariadb during compile time. # Alpine-based AMD64 (musl) does not support mysql/mariadb during compile time.
ARG DB=sqlite,postgresql ARG DB=sqlite,postgresql
@ -82,7 +82,6 @@ RUN apk add --no-cache \
openssl \ openssl \
curl \ curl \
dumb-init \ dumb-init \
sqlite \
postgresql-libs \ postgresql-libs \
ca-certificates ca-certificates
@ -93,9 +92,10 @@ EXPOSE 3012
# Copies the files from the context (Rocket.toml file and web-vault) # Copies the files from the context (Rocket.toml file and web-vault)
# and the binary from the "build" stage to the current stage # and the binary from the "build" stage to the current stage
WORKDIR /
COPY Rocket.toml . COPY Rocket.toml .
COPY --from=vault /web-vault ./web-vault COPY --from=vault /web-vault ./web-vault
COPY --from=build /app/target/x86_64-unknown-linux-musl/release/bitwarden_rs . COPY --from=build /app/target/x86_64-unknown-linux-musl/release/vaultwarden .
COPY docker/healthcheck.sh /healthcheck.sh COPY docker/healthcheck.sh /healthcheck.sh
COPY docker/start.sh /start.sh COPY docker/start.sh /start.sh
@ -103,6 +103,5 @@ COPY docker/start.sh /start.sh
HEALTHCHECK --interval=60s --timeout=10s CMD ["/healthcheck.sh"] HEALTHCHECK --interval=60s --timeout=10s CMD ["/healthcheck.sh"]
# Configures the startup! # Configures the startup!
WORKDIR /
ENTRYPOINT ["/usr/bin/dumb-init", "--"] ENTRYPOINT ["/usr/bin/dumb-init", "--"]
CMD ["/start.sh"] CMD ["/start.sh"]

@ -11,21 +11,21 @@
# be changed to point to a malicious image. # be changed to point to a malicious image.
# #
# To verify the current digest for a given tag name: # To verify the current digest for a given tag name:
# - From https://hub.docker.com/r/bitwardenrs/web-vault/tags, # - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
# click the tag name to view the digest of the image it currently points to. # click the tag name to view the digest of the image it currently points to.
# - From the command line: # - From the command line:
# $ docker pull bitwardenrs/web-vault:v2.19.0 # $ docker pull vaultwarden/web-vault:v2.19.0d
# $ docker image inspect --format "{{.RepoDigests}}" bitwardenrs/web-vault:v2.19.0 # $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2.19.0d
# [bitwardenrs/web-vault@sha256:8747cfaa2c6d87d1749e119dd884697e8099389aa9aca30a4d73d4ff796fe0e4] # [vaultwarden/web-vault@sha256:a7bd6bc4db33bd45f723c4b1ac90918b7f80204560683cfc8efd9efd03a9b233]
# #
# - Conversely, to get the tag name from the digest: # - Conversely, to get the tag name from the digest:
# $ docker image inspect --format "{{.RepoTags}}" bitwardenrs/web-vault@sha256:8747cfaa2c6d87d1749e119dd884697e8099389aa9aca30a4d73d4ff796fe0e4 # $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:a7bd6bc4db33bd45f723c4b1ac90918b7f80204560683cfc8efd9efd03a9b233
# [bitwardenrs/web-vault:v2.19.0] # [vaultwarden/web-vault:v2.19.0d]
# #
FROM bitwardenrs/web-vault@sha256:8747cfaa2c6d87d1749e119dd884697e8099389aa9aca30a4d73d4ff796fe0e4 as vault FROM vaultwarden/web-vault@sha256:a7bd6bc4db33bd45f723c4b1ac90918b7f80204560683cfc8efd9efd03a9b233 as vault
########################## BUILD IMAGE ########################## ########################## BUILD IMAGE ##########################
FROM rust:1.50 as build FROM rust:1.51 as build
# Debian-based builds support multidb # Debian-based builds support multidb
ARG DB=sqlite,mysql,postgresql ARG DB=sqlite,mysql,postgresql
@ -129,7 +129,6 @@ RUN apt-get update && apt-get install -y \
ca-certificates \ ca-certificates \
curl \ curl \
dumb-init \ dumb-init \
sqlite3 \
libmariadb-dev-compat \ libmariadb-dev-compat \
libpq5 \ libpq5 \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
@ -144,9 +143,10 @@ EXPOSE 3012
# Copies the files from the context (Rocket.toml file and web-vault) # Copies the files from the context (Rocket.toml file and web-vault)
# and the binary from the "build" stage to the current stage # and the binary from the "build" stage to the current stage
WORKDIR /
COPY Rocket.toml . COPY Rocket.toml .
COPY --from=vault /web-vault ./web-vault COPY --from=vault /web-vault ./web-vault
COPY --from=build /app/target/aarch64-unknown-linux-gnu/release/bitwarden_rs . COPY --from=build /app/target/aarch64-unknown-linux-gnu/release/vaultwarden .
COPY docker/healthcheck.sh /healthcheck.sh COPY docker/healthcheck.sh /healthcheck.sh
COPY docker/start.sh /start.sh COPY docker/start.sh /start.sh
@ -154,6 +154,5 @@ COPY docker/start.sh /start.sh
HEALTHCHECK --interval=60s --timeout=10s CMD ["/healthcheck.sh"] HEALTHCHECK --interval=60s --timeout=10s CMD ["/healthcheck.sh"]
# Configures the startup! # Configures the startup!
WORKDIR /
ENTRYPOINT ["/usr/bin/dumb-init", "--"] ENTRYPOINT ["/usr/bin/dumb-init", "--"]
CMD ["/start.sh"] CMD ["/start.sh"]

@ -11,21 +11,21 @@
# be changed to point to a malicious image. # be changed to point to a malicious image.
# #
# To verify the current digest for a given tag name: # To verify the current digest for a given tag name:
# - From https://hub.docker.com/r/bitwardenrs/web-vault/tags, # - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
# click the tag name to view the digest of the image it currently points to. # click the tag name to view the digest of the image it currently points to.
# - From the command line: # - From the command line:
# $ docker pull bitwardenrs/web-vault:v2.19.0 # $ docker pull vaultwarden/web-vault:v2.19.0d
# $ docker image inspect --format "{{.RepoDigests}}" bitwardenrs/web-vault:v2.19.0 # $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2.19.0d
# [bitwardenrs/web-vault@sha256:8747cfaa2c6d87d1749e119dd884697e8099389aa9aca30a4d73d4ff796fe0e4] # [vaultwarden/web-vault@sha256:a7bd6bc4db33bd45f723c4b1ac90918b7f80204560683cfc8efd9efd03a9b233]
# #
# - Conversely, to get the tag name from the digest: # - Conversely, to get the tag name from the digest:
# $ docker image inspect --format "{{.RepoTags}}" bitwardenrs/web-vault@sha256:8747cfaa2c6d87d1749e119dd884697e8099389aa9aca30a4d73d4ff796fe0e4 # $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:a7bd6bc4db33bd45f723c4b1ac90918b7f80204560683cfc8efd9efd03a9b233
# [bitwardenrs/web-vault:v2.19.0] # [vaultwarden/web-vault:v2.19.0d]
# #
FROM bitwardenrs/web-vault@sha256:8747cfaa2c6d87d1749e119dd884697e8099389aa9aca30a4d73d4ff796fe0e4 as vault FROM vaultwarden/web-vault@sha256:a7bd6bc4db33bd45f723c4b1ac90918b7f80204560683cfc8efd9efd03a9b233 as vault
########################## BUILD IMAGE ########################## ########################## BUILD IMAGE ##########################
FROM rust:1.50 as build FROM rust:1.51 as build
# Debian-based builds support multidb # Debian-based builds support multidb
ARG DB=sqlite,mysql,postgresql ARG DB=sqlite,mysql,postgresql
@ -129,7 +129,6 @@ RUN apt-get update && apt-get install -y \
ca-certificates \ ca-certificates \
curl \ curl \
dumb-init \ dumb-init \
sqlite3 \
libmariadb-dev-compat \ libmariadb-dev-compat \
libpq5 \ libpq5 \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
@ -144,9 +143,10 @@ EXPOSE 3012
# Copies the files from the context (Rocket.toml file and web-vault) # Copies the files from the context (Rocket.toml file and web-vault)
# and the binary from the "build" stage to the current stage # and the binary from the "build" stage to the current stage
WORKDIR /
COPY Rocket.toml . COPY Rocket.toml .
COPY --from=vault /web-vault ./web-vault COPY --from=vault /web-vault ./web-vault
COPY --from=build /app/target/arm-unknown-linux-gnueabi/release/bitwarden_rs . COPY --from=build /app/target/arm-unknown-linux-gnueabi/release/vaultwarden .
COPY docker/healthcheck.sh /healthcheck.sh COPY docker/healthcheck.sh /healthcheck.sh
COPY docker/start.sh /start.sh COPY docker/start.sh /start.sh
@ -154,6 +154,5 @@ COPY docker/start.sh /start.sh
HEALTHCHECK --interval=60s --timeout=10s CMD ["/healthcheck.sh"] HEALTHCHECK --interval=60s --timeout=10s CMD ["/healthcheck.sh"]
# Configures the startup! # Configures the startup!
WORKDIR /
ENTRYPOINT ["/usr/bin/dumb-init", "--"] ENTRYPOINT ["/usr/bin/dumb-init", "--"]
CMD ["/start.sh"] CMD ["/start.sh"]

@ -11,21 +11,21 @@
# be changed to point to a malicious image. # be changed to point to a malicious image.
# #
# To verify the current digest for a given tag name: # To verify the current digest for a given tag name:
# - From https://hub.docker.com/r/bitwardenrs/web-vault/tags, # - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
# click the tag name to view the digest of the image it currently points to. # click the tag name to view the digest of the image it currently points to.
# - From the command line: # - From the command line:
# $ docker pull bitwardenrs/web-vault:v2.19.0 # $ docker pull vaultwarden/web-vault:v2.19.0d
# $ docker image inspect --format "{{.RepoDigests}}" bitwardenrs/web-vault:v2.19.0 # $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2.19.0d
# [bitwardenrs/web-vault@sha256:8747cfaa2c6d87d1749e119dd884697e8099389aa9aca30a4d73d4ff796fe0e4] # [vaultwarden/web-vault@sha256:a7bd6bc4db33bd45f723c4b1ac90918b7f80204560683cfc8efd9efd03a9b233]
# #
# - Conversely, to get the tag name from the digest: # - Conversely, to get the tag name from the digest:
# $ docker image inspect --format "{{.RepoTags}}" bitwardenrs/web-vault@sha256:8747cfaa2c6d87d1749e119dd884697e8099389aa9aca30a4d73d4ff796fe0e4 # $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:a7bd6bc4db33bd45f723c4b1ac90918b7f80204560683cfc8efd9efd03a9b233
# [bitwardenrs/web-vault:v2.19.0] # [vaultwarden/web-vault:v2.19.0d]
# #
FROM bitwardenrs/web-vault@sha256:8747cfaa2c6d87d1749e119dd884697e8099389aa9aca30a4d73d4ff796fe0e4 as vault FROM vaultwarden/web-vault@sha256:a7bd6bc4db33bd45f723c4b1ac90918b7f80204560683cfc8efd9efd03a9b233 as vault
########################## BUILD IMAGE ########################## ########################## BUILD IMAGE ##########################
FROM rust:1.50 as build FROM rust:1.51 as build
# Debian-based builds support multidb # Debian-based builds support multidb
ARG DB=sqlite,mysql,postgresql ARG DB=sqlite,mysql,postgresql
@ -129,7 +129,6 @@ RUN apt-get update && apt-get install -y \
ca-certificates \ ca-certificates \
curl \ curl \
dumb-init \ dumb-init \
sqlite3 \
libmariadb-dev-compat \ libmariadb-dev-compat \
libpq5 \ libpq5 \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
@ -144,9 +143,10 @@ EXPOSE 3012
# Copies the files from the context (Rocket.toml file and web-vault) # Copies the files from the context (Rocket.toml file and web-vault)
# and the binary from the "build" stage to the current stage # and the binary from the "build" stage to the current stage
WORKDIR /
COPY Rocket.toml . COPY Rocket.toml .
COPY --from=vault /web-vault ./web-vault COPY --from=vault /web-vault ./web-vault
COPY --from=build /app/target/armv7-unknown-linux-gnueabihf/release/bitwarden_rs . COPY --from=build /app/target/armv7-unknown-linux-gnueabihf/release/vaultwarden .
COPY docker/healthcheck.sh /healthcheck.sh COPY docker/healthcheck.sh /healthcheck.sh
COPY docker/start.sh /start.sh COPY docker/start.sh /start.sh
@ -154,6 +154,5 @@ COPY docker/start.sh /start.sh
HEALTHCHECK --interval=60s --timeout=10s CMD ["/healthcheck.sh"] HEALTHCHECK --interval=60s --timeout=10s CMD ["/healthcheck.sh"]
# Configures the startup! # Configures the startup!
WORKDIR /
ENTRYPOINT ["/usr/bin/dumb-init", "--"] ENTRYPOINT ["/usr/bin/dumb-init", "--"]
CMD ["/start.sh"] CMD ["/start.sh"]

@ -11,18 +11,18 @@
# be changed to point to a malicious image. # be changed to point to a malicious image.
# #
# To verify the current digest for a given tag name: # To verify the current digest for a given tag name:
# - From https://hub.docker.com/r/bitwardenrs/web-vault/tags, # - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
# click the tag name to view the digest of the image it currently points to. # click the tag name to view the digest of the image it currently points to.
# - From the command line: # - From the command line:
# $ docker pull bitwardenrs/web-vault:v2.19.0 # $ docker pull vaultwarden/web-vault:v2.19.0d
# $ docker image inspect --format "{{.RepoDigests}}" bitwardenrs/web-vault:v2.19.0 # $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2.19.0d
# [bitwardenrs/web-vault@sha256:8747cfaa2c6d87d1749e119dd884697e8099389aa9aca30a4d73d4ff796fe0e4] # [vaultwarden/web-vault@sha256:a7bd6bc4db33bd45f723c4b1ac90918b7f80204560683cfc8efd9efd03a9b233]
# #
# - Conversely, to get the tag name from the digest: # - Conversely, to get the tag name from the digest:
# $ docker image inspect --format "{{.RepoTags}}" bitwardenrs/web-vault@sha256:8747cfaa2c6d87d1749e119dd884697e8099389aa9aca30a4d73d4ff796fe0e4 # $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:a7bd6bc4db33bd45f723c4b1ac90918b7f80204560683cfc8efd9efd03a9b233
# [bitwardenrs/web-vault:v2.19.0] # [vaultwarden/web-vault:v2.19.0d]
# #
FROM bitwardenrs/web-vault@sha256:8747cfaa2c6d87d1749e119dd884697e8099389aa9aca30a4d73d4ff796fe0e4 as vault FROM vaultwarden/web-vault@sha256:a7bd6bc4db33bd45f723c4b1ac90918b7f80204560683cfc8efd9efd03a9b233 as vault
########################## BUILD IMAGE ########################## ########################## BUILD IMAGE ##########################
FROM messense/rust-musl-cross:armv7-musleabihf as build FROM messense/rust-musl-cross:armv7-musleabihf as build
@ -67,7 +67,7 @@ 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
RUN cargo build --features ${DB} --release --target=armv7-unknown-linux-musleabihf RUN cargo build --features ${DB} --release --target=armv7-unknown-linux-musleabihf
RUN musl-strip target/armv7-unknown-linux-musleabihf/release/bitwarden_rs RUN musl-strip 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
@ -86,7 +86,6 @@ RUN apk add --no-cache \
openssl \ openssl \
curl \ curl \
dumb-init \ dumb-init \
sqlite \
ca-certificates ca-certificates
RUN mkdir /data RUN mkdir /data
@ -99,9 +98,10 @@ EXPOSE 3012
# Copies the files from the context (Rocket.toml file and web-vault) # Copies the files from the context (Rocket.toml file and web-vault)
# and the binary from the "build" stage to the current stage # and the binary from the "build" stage to the current stage
WORKDIR /
COPY Rocket.toml . COPY Rocket.toml .
COPY --from=vault /web-vault ./web-vault COPY --from=vault /web-vault ./web-vault
COPY --from=build /app/target/armv7-unknown-linux-musleabihf/release/bitwarden_rs . COPY --from=build /app/target/armv7-unknown-linux-musleabihf/release/vaultwarden .
COPY docker/healthcheck.sh /healthcheck.sh COPY docker/healthcheck.sh /healthcheck.sh
COPY docker/start.sh /start.sh COPY docker/start.sh /start.sh
@ -109,6 +109,5 @@ COPY docker/start.sh /start.sh
HEALTHCHECK --interval=60s --timeout=10s CMD ["/healthcheck.sh"] HEALTHCHECK --interval=60s --timeout=10s CMD ["/healthcheck.sh"]
# Configures the startup! # Configures the startup!
WORKDIR /
ENTRYPOINT ["/usr/bin/dumb-init", "--"] ENTRYPOINT ["/usr/bin/dumb-init", "--"]
CMD ["/start.sh"] CMD ["/start.sh"]

@ -1,15 +1,15 @@
#!/bin/sh #!/bin/sh
if [ -r /etc/bitwarden_rs.sh ]; then if [ -r /etc/vaultwarden.sh ]; then
. /etc/bitwarden_rs.sh . /etc/vaultwarden.sh
fi fi
if [ -d /etc/bitwarden_rs.d ]; then if [ -d /etc/vaultwarden.d ]; then
for f in /etc/bitwarden_rs.d/*.sh; do for f in /etc/vaultwarden.d/*.sh; do
if [ -r $f ]; then if [ -r $f ]; then
. $f . $f
fi fi
done done
fi fi
exec /bitwarden_rs "${@}" exec /vaultwarden "${@}"

@ -10,7 +10,7 @@ Docker Hub hooks provide these predefined [environment variables](https://docs.d
* `DOCKER_TAG`: the Docker repository tag being built. * `DOCKER_TAG`: the Docker repository tag being built.
* `IMAGE_NAME`: the name and tag of the Docker repository being built. (This variable is a combination of `DOCKER_REPO:DOCKER_TAG`.) * `IMAGE_NAME`: the name and tag of the Docker repository being built. (This variable is a combination of `DOCKER_REPO:DOCKER_TAG`.)
The current multi-arch image build relies on the original bitwarden_rs Dockerfiles, which use cross-compilation for architectures other than `amd64`, and don't yet support all arch/distro combinations. However, cross-compilation is much faster than QEMU-based builds (e.g., using `docker buildx`). This situation may need to be revisited at some point. The current multi-arch image build relies on the original vaultwarden Dockerfiles, which use cross-compilation for architectures other than `amd64`, and don't yet support all arch/distro combinations. However, cross-compilation is much faster than QEMU-based builds (e.g., using `docker buildx`). This situation may need to be revisited at some point.
## References ## References

@ -22,7 +22,7 @@ fi
LABELS=( 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/bitwarden_rs/wiki" org.opencontainers.image.documentation="https://github.com/dani-garcia/vaultwarden/wiki"
org.opencontainers.image.licenses="GPL-3.0-only" org.opencontainers.image.licenses="GPL-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}"

@ -103,7 +103,7 @@ docker buildx build \
# (https://github.com/moby/moby/issues/41017). # (https://github.com/moby/moby/issues/41017).
# #
# Note that we use `arm32v6` instead of `armv6` to be consistent with the # Note that we use `arm32v6` instead of `armv6` to be consistent with the
# existing bitwarden_rs tags, which adhere to the naming conventions of the # existing vaultwarden tags, which adhere to the naming conventions of the
# Docker per-architecture repos (e.g., https://hub.docker.com/u/arm32v6). # Docker per-architecture repos (e.g., https://hub.docker.com/u/arm32v6).
# Unfortunately, these per-arch repo names aren't always consistent with the # Unfortunately, these per-arch repo names aren't always consistent with the
# corresponding platform (OS/arch/variant) IDs, particularly in the case of # corresponding platform (OS/arch/variant) IDs, particularly in the case of

@ -1 +1 @@
nightly-2021-02-22 nightly-2021-04-14

@ -1,2 +1,7 @@
version = "Two" version = "Two"
edition = "2018"
max_width = 120 max_width = 120
newline_style = "Unix"
use_small_heuristics = "Off"
struct_lit_single_line = false
overflow_delimited_expr = true

@ -1,9 +1,8 @@
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
use serde_json::Value; use serde_json::Value;
use std::{env, process::Command, time::Duration}; use std::{env, time::Duration};
use reqwest::{blocking::Client, header::USER_AGENT};
use rocket::{ use rocket::{
http::{Cookie, Cookies, SameSite}, http::{Cookie, Cookies, SameSite},
request::{self, FlashMessage, Form, FromRequest, Outcome, Request}, request::{self, FlashMessage, Form, FromRequest, Outcome, Request},
@ -19,7 +18,7 @@ use crate::{
db::{backup_database, get_sql_server_version, models::*, DbConn, DbConnType}, db::{backup_database, get_sql_server_version, models::*, DbConn, DbConnType},
error::{Error, MapResult}, error::{Error, MapResult},
mail, mail,
util::{format_naive_datetime_local, get_display_size, is_running_in_docker}, util::{format_naive_datetime_local, get_display_size, get_reqwest_client, is_running_in_docker},
CONFIG, CONFIG,
}; };
@ -64,12 +63,8 @@ static DB_TYPE: Lazy<&str> = Lazy::new(|| {
.unwrap_or("Unknown") .unwrap_or("Unknown")
}); });
static CAN_BACKUP: Lazy<bool> = Lazy::new(|| { static CAN_BACKUP: Lazy<bool> =
DbConnType::from_url(&CONFIG.database_url()) Lazy::new(|| DbConnType::from_url(&CONFIG.database_url()).map(|t| t == DbConnType::sqlite).unwrap_or(false));
.map(|t| t == DbConnType::sqlite)
.unwrap_or(false)
&& Command::new("sqlite3").arg("-version").status().is_ok()
});
#[get("/")] #[get("/")]
fn admin_disabled() -> &'static str { fn admin_disabled() -> &'static str {
@ -142,7 +137,12 @@ fn admin_url(referer: Referer) -> String {
fn admin_login(flash: Option<FlashMessage>) -> ApiResult<Html<String>> { fn admin_login(flash: Option<FlashMessage>) -> ApiResult<Html<String>> {
// If there is an error, show it // If there is an error, show it
let msg = flash.map(|msg| format!("{}: {}", msg.name(), msg.msg())); let msg = flash.map(|msg| format!("{}: {}", msg.name(), msg.msg()));
let json = json!({"page_content": "admin/login", "version": VERSION, "error": msg, "urlpath": CONFIG.domain_path()}); let json = json!({
"page_content": "admin/login",
"version": VERSION,
"error": msg,
"urlpath": CONFIG.domain_path()
});
// Return the page // Return the page
let text = CONFIG.render_template(BASE_TEMPLATE, &json)?; let text = CONFIG.render_template(BASE_TEMPLATE, &json)?;
@ -166,10 +166,7 @@ fn post_admin_login(
// If the token is invalid, redirect to login page // If the token is invalid, redirect to login page
if !_validate_token(&data.token) { if !_validate_token(&data.token) {
error!("Invalid admin token. IP: {}", ip.ip); error!("Invalid admin token. IP: {}", ip.ip);
Err(Flash::error( Err(Flash::error(Redirect::to(admin_url(referer)), "Invalid admin token, please try again."))
Redirect::to(admin_url(referer)),
"Invalid admin token, please try again.",
))
} else { } else {
// If the token received is valid, generate JWT and save it as a cookie // If the token received is valid, generate JWT and save it as a cookie
let claims = generate_admin_claims(); let claims = generate_admin_claims();
@ -329,7 +326,8 @@ fn get_users_json(_token: AdminToken, conn: DbConn) -> Json<Value> {
fn users_overview(_token: AdminToken, conn: DbConn) -> ApiResult<Html<String>> { fn users_overview(_token: AdminToken, conn: DbConn) -> ApiResult<Html<String>> {
let users = User::get_all(&conn); let users = User::get_all(&conn);
let dt_fmt = "%Y-%m-%d %H:%M:%S %Z"; let dt_fmt = "%Y-%m-%d %H:%M:%S %Z";
let users_json: Vec<Value> = users.iter() let users_json: Vec<Value> = users
.iter()
.map(|u| { .map(|u| {
let mut usr = u.to_json(&conn); let mut usr = u.to_json(&conn);
usr["cipher_count"] = json!(Cipher::count_owned_by_user(&u.uuid, &conn)); usr["cipher_count"] = json!(Cipher::count_owned_by_user(&u.uuid, &conn));
@ -339,7 +337,7 @@ fn users_overview(_token: AdminToken, conn: DbConn) -> ApiResult<Html<String>> {
usr["created_at"] = json!(format_naive_datetime_local(&u.created_at, dt_fmt)); usr["created_at"] = json!(format_naive_datetime_local(&u.created_at, dt_fmt));
usr["last_active"] = match u.last_active(&conn) { usr["last_active"] = match u.last_active(&conn) {
Some(dt) => json!(format_naive_datetime_local(&dt, dt_fmt)), Some(dt) => json!(format_naive_datetime_local(&dt, dt_fmt)),
None => json!("Never") None => json!("Never"),
}; };
usr usr
}) })
@ -424,7 +422,6 @@ fn update_user_org_type(data: Json<UserOrgTypeData>, _token: AdminToken, conn: D
user_to_edit.save(&conn) user_to_edit.save(&conn)
} }
#[post("/users/update_revision")] #[post("/users/update_revision")]
fn update_revision_users(_token: AdminToken, conn: DbConn) -> EmptyResult { fn update_revision_users(_token: AdminToken, conn: DbConn) -> EmptyResult {
User::update_all_revisions(&conn) User::update_all_revisions(&conn)
@ -433,7 +430,8 @@ fn update_revision_users(_token: AdminToken, conn: DbConn) -> EmptyResult {
#[get("/organizations/overview")] #[get("/organizations/overview")]
fn organizations_overview(_token: AdminToken, conn: DbConn) -> ApiResult<Html<String>> { fn organizations_overview(_token: AdminToken, conn: DbConn) -> ApiResult<Html<String>> {
let organizations = Organization::get_all(&conn); let organizations = Organization::get_all(&conn);
let organizations_json: Vec<Value> = organizations.iter() let organizations_json: Vec<Value> = organizations
.iter()
.map(|o| { .map(|o| {
let mut org = o.to_json(); let mut org = o.to_json();
org["user_count"] = json!(UserOrganization::count_by_org(&o.uuid, &conn)); org["user_count"] = json!(UserOrganization::count_by_org(&o.uuid, &conn));
@ -470,26 +468,15 @@ struct GitCommit {
} }
fn get_github_api<T: DeserializeOwned>(url: &str) -> Result<T, Error> { fn get_github_api<T: DeserializeOwned>(url: &str) -> Result<T, Error> {
let github_api = Client::builder().build()?; let github_api = get_reqwest_client();
Ok(github_api Ok(github_api.get(url).timeout(Duration::from_secs(10)).send()?.error_for_status()?.json::<T>()?)
.get(url)
.timeout(Duration::from_secs(10))
.header(USER_AGENT, "Bitwarden_RS")
.send()?
.error_for_status()?
.json::<T>()?)
} }
fn has_http_access() -> bool { fn has_http_access() -> bool {
let http_access = Client::builder().build().unwrap(); let http_access = get_reqwest_client();
match http_access match http_access.head("https://github.com/dani-garcia/vaultwarden").timeout(Duration::from_secs(10)).send() {
.head("https://github.com/dani-garcia/bitwarden_rs")
.timeout(Duration::from_secs(10))
.header(USER_AGENT, "Bitwarden_RS")
.send()
{
Ok(r) => r.status().is_success(), Ok(r) => r.status().is_success(),
_ => false, _ => false,
} }
@ -502,9 +489,16 @@ fn diagnostics(_token: AdminToken, ip_header: IpHeader, conn: DbConn) -> ApiResu
use std::net::ToSocketAddrs; use std::net::ToSocketAddrs;
// Get current running versions // Get current running versions
let vault_version_path = format!("{}/{}", CONFIG.web_vault_folder(), "version.json"); let web_vault_version: WebVaultVersion =
let vault_version_str = read_file_string(&vault_version_path)?; match read_file_string(&format!("{}/{}", CONFIG.web_vault_folder(), "bwrs-version.json")) {
let web_vault_version: WebVaultVersion = serde_json::from_str(&vault_version_str)?; Ok(s) => serde_json::from_str(&s)?,
_ => match read_file_string(&format!("{}/{}", CONFIG.web_vault_folder(), "version.json")) {
Ok(s) => serde_json::from_str(&s)?,
_ => WebVaultVersion {
version: String::from("Version file missing"),
},
},
};
// Execute some environment checks // Execute some environment checks
let running_within_docker = is_running_in_docker(); let running_within_docker = is_running_in_docker();
@ -524,11 +518,11 @@ fn diagnostics(_token: AdminToken, ip_header: IpHeader, conn: DbConn) -> ApiResu
// TODO: Maybe we need to cache this using a LazyStatic or something. Github only allows 60 requests per hour, and we use 3 here already. // TODO: Maybe we need to cache this using a LazyStatic or something. Github only allows 60 requests per hour, and we use 3 here already.
let (latest_release, latest_commit, latest_web_build) = if has_http_access { let (latest_release, latest_commit, latest_web_build) = if has_http_access {
( (
match get_github_api::<GitRelease>("https://api.github.com/repos/dani-garcia/bitwarden_rs/releases/latest") { match get_github_api::<GitRelease>("https://api.github.com/repos/dani-garcia/vaultwarden/releases/latest") {
Ok(r) => r.tag_name, Ok(r) => r.tag_name,
_ => "-".to_string(), _ => "-".to_string(),
}, },
match get_github_api::<GitCommit>("https://api.github.com/repos/dani-garcia/bitwarden_rs/commits/master") { match get_github_api::<GitCommit>("https://api.github.com/repos/dani-garcia/vaultwarden/commits/main") {
Ok(mut c) => { Ok(mut c) => {
c.sha.truncate(8); c.sha.truncate(8);
c.sha c.sha
@ -540,7 +534,9 @@ fn diagnostics(_token: AdminToken, ip_header: IpHeader, conn: DbConn) -> ApiResu
if running_within_docker { if running_within_docker {
"-".to_string() "-".to_string()
} else { } else {
match get_github_api::<GitRelease>("https://api.github.com/repos/dani-garcia/bw_web_builds/releases/latest") { match get_github_api::<GitRelease>(
"https://api.github.com/repos/dani-garcia/bw_web_builds/releases/latest",
) {
Ok(r) => r.tag_name.trim_start_matches('v').to_string(), Ok(r) => r.tag_name.trim_start_matches('v').to_string(),
_ => "-".to_string(), _ => "-".to_string(),
} }
@ -552,14 +548,15 @@ fn diagnostics(_token: AdminToken, ip_header: IpHeader, conn: DbConn) -> ApiResu
let ip_header_name = match &ip_header.0 { let ip_header_name = match &ip_header.0 {
Some(h) => h, Some(h) => h,
_ => "" _ => "",
}; };
let diagnostics_json = json!({ let diagnostics_json = json!({
"dns_resolved": dns_resolved, "dns_resolved": dns_resolved,
"web_vault_version": web_vault_version.version,
"latest_release": latest_release, "latest_release": latest_release,
"latest_commit": latest_commit, "latest_commit": latest_commit,
"web_vault_enabled": &CONFIG.web_vault_enabled(),
"web_vault_version": web_vault_version.version,
"latest_web_build": latest_web_build, "latest_web_build": latest_web_build,
"running_within_docker": running_within_docker, "running_within_docker": running_within_docker,
"has_http_access": has_http_access, "has_http_access": has_http_access,
@ -571,6 +568,7 @@ fn diagnostics(_token: AdminToken, ip_header: IpHeader, conn: DbConn) -> ApiResu
"db_type": *DB_TYPE, "db_type": *DB_TYPE,
"db_version": get_sql_server_version(&conn), "db_version": get_sql_server_version(&conn),
"admin_url": format!("{}/diagnostics", admin_url(Referer(None))), "admin_url": format!("{}/diagnostics", admin_url(Referer(None))),
"server_time_local": Local::now().format("%Y-%m-%d %H:%M:%S %Z").to_string(),
"server_time": Utc::now().format("%Y-%m-%d %H:%M:%S UTC").to_string(), // Run the date/time check as the last item to minimize the difference "server_time": Utc::now().format("%Y-%m-%d %H:%M:%S UTC").to_string(), // Run the date/time check as the last item to minimize the difference
}); });
@ -596,11 +594,11 @@ fn delete_config(_token: AdminToken) -> EmptyResult {
} }
#[post("/config/backup_db")] #[post("/config/backup_db")]
fn backup_db(_token: AdminToken) -> EmptyResult { fn backup_db(_token: AdminToken, conn: DbConn) -> EmptyResult {
if *CAN_BACKUP { if *CAN_BACKUP {
backup_database() backup_database(&conn)
} else { } else {
err!("Can't back up current DB (either it's not SQLite or the 'sqlite' binary is not present)"); err!("Can't back up current DB (Only SQLite supports this feature)");
} }
} }

@ -95,7 +95,7 @@ fn register(data: JsonUpcase<RegisterData>, conn: DbConn) -> EmptyResult {
} }
None => { None => {
// Order is important here; the invitation check must come first // Order is important here; the invitation check must come first
// because the bitwarden_rs admin can invite anyone, regardless // because the vaultwarden admin can invite anyone, regardless
// of other signup restrictions. // of other signup restrictions.
if Invitation::take(&data.Email, &conn) || CONFIG.is_signup_allowed(&data.Email) { if Invitation::take(&data.Email, &conn) || CONFIG.is_signup_allowed(&data.Email) {
User::new(data.Email.clone()) User::new(data.Email.clone())
@ -320,15 +320,7 @@ fn post_rotatekey(data: JsonUpcase<KeyData>, headers: Headers, conn: DbConn, nt:
err!("The cipher is not owned by the user") err!("The cipher is not owned by the user")
} }
update_cipher_from_data( update_cipher_from_data(&mut saved_cipher, cipher_data, &headers, false, &conn, &nt, UpdateType::CipherUpdate)?
&mut saved_cipher,
cipher_data,
&headers,
false,
&conn,
&nt,
UpdateType::CipherUpdate,
)?
} }
// Update user data // Update user data

@ -13,7 +13,7 @@ use crate::{
api::{self, EmptyResult, JsonResult, JsonUpcase, Notify, PasswordData, UpdateType}, api::{self, EmptyResult, JsonResult, JsonUpcase, Notify, PasswordData, UpdateType},
auth::Headers, auth::Headers,
crypto, crypto,
db::{models::*, DbConn}, db::{models::*, DbConn, DbPool},
CONFIG, CONFIG,
}; };
@ -25,7 +25,7 @@ pub fn routes() -> Vec<Route> {
// whether the user is an owner/admin of the relevant org, and if so, // whether the user is an owner/admin of the relevant org, and if so,
// allows the operation unconditionally. // allows the operation unconditionally.
// //
// bitwarden_rs factors in the org owner/admin status as part of // vaultwarden factors in the org owner/admin status as part of
// determining the write accessibility of a cipher, so most // determining the write accessibility of a cipher, so most
// admin/non-admin implementations can be shared. // admin/non-admin implementations can be shared.
routes![ routes![
@ -77,6 +77,15 @@ pub fn routes() -> Vec<Route> {
] ]
} }
pub fn purge_trashed_ciphers(pool: DbPool) {
debug!("Purging trashed ciphers");
if let Ok(conn) = pool.get() {
Cipher::purge_trash(&conn);
} else {
error!("Failed to get DB connection while purging trashed ciphers")
}
}
#[derive(FromForm, Default)] #[derive(FromForm, Default)]
struct SyncData { struct SyncData {
#[form(field = "excludeDomains")] #[form(field = "excludeDomains")]
@ -91,24 +100,18 @@ fn sync(data: Form<SyncData>, headers: Headers, conn: DbConn) -> Json<Value> {
let folders_json: Vec<Value> = folders.iter().map(Folder::to_json).collect(); let folders_json: Vec<Value> = folders.iter().map(Folder::to_json).collect();
let collections = Collection::find_by_user_uuid(&headers.user.uuid, &conn); let collections = Collection::find_by_user_uuid(&headers.user.uuid, &conn);
let collections_json: Vec<Value> = collections.iter() let collections_json: Vec<Value> =
.map(|c| c.to_json_details(&headers.user.uuid, &conn)) collections.iter().map(|c| c.to_json_details(&headers.user.uuid, &conn)).collect();
.collect();
let policies = OrgPolicy::find_by_user(&headers.user.uuid, &conn); let policies = OrgPolicy::find_by_user(&headers.user.uuid, &conn);
let policies_json: Vec<Value> = policies.iter().map(OrgPolicy::to_json).collect(); let policies_json: Vec<Value> = policies.iter().map(OrgPolicy::to_json).collect();
let ciphers = Cipher::find_by_user_visible(&headers.user.uuid, &conn); let ciphers = Cipher::find_by_user_visible(&headers.user.uuid, &conn);
let ciphers_json: Vec<Value> = ciphers let ciphers_json: Vec<Value> =
.iter() ciphers.iter().map(|c| c.to_json(&headers.host, &headers.user.uuid, &conn)).collect();
.map(|c| c.to_json(&headers.host, &headers.user.uuid, &conn))
.collect();
let sends = Send::find_by_user(&headers.user.uuid, &conn); let sends = Send::find_by_user(&headers.user.uuid, &conn);
let sends_json: Vec<Value> = sends let sends_json: Vec<Value> = sends.iter().map(|s| s.to_json()).collect();
.iter()
.map(|s| s.to_json())
.collect();
let domains_json = if data.exclude_domains { let domains_json = if data.exclude_domains {
Value::Null Value::Null
@ -124,6 +127,7 @@ fn sync(data: Form<SyncData>, headers: Headers, conn: DbConn) -> Json<Value> {
"Ciphers": ciphers_json, "Ciphers": ciphers_json,
"Domains": domains_json, "Domains": domains_json,
"Sends": sends_json, "Sends": sends_json,
"unofficialServer": true,
"Object": "sync" "Object": "sync"
})) }))
} }
@ -132,10 +136,8 @@ fn sync(data: Form<SyncData>, headers: Headers, conn: DbConn) -> Json<Value> {
fn get_ciphers(headers: Headers, conn: DbConn) -> Json<Value> { fn get_ciphers(headers: Headers, conn: DbConn) -> Json<Value> {
let ciphers = Cipher::find_by_user_visible(&headers.user.uuid, &conn); let ciphers = Cipher::find_by_user_visible(&headers.user.uuid, &conn);
let ciphers_json: Vec<Value> = ciphers let ciphers_json: Vec<Value> =
.iter() ciphers.iter().map(|c| c.to_json(&headers.host, &headers.user.uuid, &conn)).collect();
.map(|c| c.to_json(&headers.host, &headers.user.uuid, &conn))
.collect();
Json(json!({ Json(json!({
"Data": ciphers_json, "Data": ciphers_json,
@ -236,7 +238,7 @@ fn post_ciphers_create(data: JsonUpcase<ShareCipherData>, headers: Headers, conn
// Check if there are one more more collections selected when this cipher is part of an organization. // Check if there are one more more collections selected when this cipher is part of an organization.
// err if this is not the case before creating an empty cipher. // err if this is not the case before creating an empty cipher.
if data.Cipher.OrganizationId.is_some() && data.CollectionIds.is_empty() { if data.Cipher.OrganizationId.is_some() && data.CollectionIds.is_empty() {
err!("You must select at least one collection."); err!("You must select at least one collection.");
} }
@ -278,17 +280,12 @@ fn post_ciphers(data: JsonUpcase<CipherData>, headers: Headers, conn: DbConn, nt
/// allowed to delete or share such ciphers to an org, however. /// allowed to delete or share such ciphers to an org, however.
/// ///
/// Ref: https://bitwarden.com/help/article/policies/#personal-ownership /// Ref: https://bitwarden.com/help/article/policies/#personal-ownership
fn enforce_personal_ownership_policy( fn enforce_personal_ownership_policy(data: &CipherData, headers: &Headers, conn: &DbConn) -> EmptyResult {
data: &CipherData,
headers: &Headers,
conn: &DbConn
) -> EmptyResult {
if data.OrganizationId.is_none() { if data.OrganizationId.is_none() {
let user_uuid = &headers.user.uuid; let user_uuid = &headers.user.uuid;
let policy_type = OrgPolicyType::PersonalOwnership; let policy_type = OrgPolicyType::PersonalOwnership;
if OrgPolicy::is_applicable_to_user(user_uuid, policy_type, conn) { if OrgPolicy::is_applicable_to_user(user_uuid, policy_type, conn) {
err!("Due to an Enterprise Policy, you are restricted from \ err!("Due to an Enterprise Policy, you are restricted from saving items to your personal vault.")
saving items to your personal vault.")
} }
} }
Ok(()) Ok(())
@ -307,11 +304,12 @@ pub fn update_cipher_from_data(
// Check that the client isn't updating an existing cipher with stale data. // Check that the client isn't updating an existing cipher with stale data.
if let Some(dt) = data.LastKnownRevisionDate { if let Some(dt) = data.LastKnownRevisionDate {
match NaiveDateTime::parse_from_str(&dt, "%+") { // ISO 8601 format match NaiveDateTime::parse_from_str(&dt, "%+") {
Err(err) => // ISO 8601 format
warn!("Error parsing LastKnownRevisionDate '{}': {}", dt, err), Err(err) => warn!("Error parsing LastKnownRevisionDate '{}': {}", dt, err),
Ok(dt) if cipher.updated_at.signed_duration_since(dt).num_seconds() > 1 => Ok(dt) if cipher.updated_at.signed_duration_since(dt).num_seconds() > 1 => {
err!("The client copy of this cipher is out of date. Resync the client and try again."), err!("The client copy of this cipher is out of date. Resync the client and try again.")
}
Ok(_) => (), Ok(_) => (),
} }
} }
@ -384,12 +382,9 @@ pub fn update_cipher_from_data(
// But, we at least know we do not need to store and return this specific key. // But, we at least know we do not need to store and return this specific key.
fn _clean_cipher_data(mut json_data: Value) -> Value { fn _clean_cipher_data(mut json_data: Value) -> Value {
if json_data.is_array() { if json_data.is_array() {
json_data.as_array_mut() json_data.as_array_mut().unwrap().iter_mut().for_each(|ref mut f| {
.unwrap() f.as_object_mut().unwrap().remove("Response");
.iter_mut() });
.for_each(|ref mut f| {
f.as_object_mut().unwrap().remove("Response");
});
}; };
json_data json_data
} }
@ -411,13 +406,13 @@ pub fn update_cipher_from_data(
data["Uris"] = _clean_cipher_data(data["Uris"].clone()); data["Uris"] = _clean_cipher_data(data["Uris"].clone());
} }
data data
}, }
None => err!("Data missing"), None => err!("Data missing"),
}; };
cipher.name = data.Name; cipher.name = data.Name;
cipher.notes = data.Notes; cipher.notes = data.Notes;
cipher.fields = data.Fields.map(|f| _clean_cipher_data(f).to_string() ); cipher.fields = data.Fields.map(|f| _clean_cipher_data(f).to_string());
cipher.data = type_data.to_string(); cipher.data = type_data.to_string();
cipher.password_history = data.PasswordHistory.map(|f| f.to_string()); cipher.password_history = data.PasswordHistory.map(|f| f.to_string());
@ -592,11 +587,8 @@ fn post_collections_admin(
} }
let posted_collections: HashSet<String> = data.CollectionIds.iter().cloned().collect(); let posted_collections: HashSet<String> = data.CollectionIds.iter().cloned().collect();
let current_collections: HashSet<String> = cipher let current_collections: HashSet<String> =
.get_collections(&headers.user.uuid, &conn) cipher.get_collections(&headers.user.uuid, &conn).iter().cloned().collect();
.iter()
.cloned()
.collect();
for collection in posted_collections.symmetric_difference(&current_collections) { for collection in posted_collections.symmetric_difference(&current_collections) {
match Collection::find_by_uuid(&collection, &conn) { match Collection::find_by_uuid(&collection, &conn) {
@ -832,24 +824,25 @@ fn post_attachment(
let file_name = HEXLOWER.encode(&crypto::get_random(vec![0; 10])); let file_name = HEXLOWER.encode(&crypto::get_random(vec![0; 10]));
let path = base_path.join(&file_name); let path = base_path.join(&file_name);
let size = match field.data.save().memory_threshold(0).size_limit(size_limit).with_path(path.clone()) { let size =
SaveResult::Full(SavedData::File(_, size)) => size as i32, match field.data.save().memory_threshold(0).size_limit(size_limit).with_path(path.clone()) {
SaveResult::Full(other) => { SaveResult::Full(SavedData::File(_, size)) => size as i32,
std::fs::remove_file(path).ok(); SaveResult::Full(other) => {
error = Some(format!("Attachment is not a file: {:?}", other)); std::fs::remove_file(path).ok();
return; error = Some(format!("Attachment is not a file: {:?}", other));
} return;
SaveResult::Partial(_, reason) => { }
std::fs::remove_file(path).ok(); SaveResult::Partial(_, reason) => {
error = Some(format!("Attachment size limit exceeded with this file: {:?}", reason)); std::fs::remove_file(path).ok();
return; error = Some(format!("Attachment size limit exceeded with this file: {:?}", reason));
} return;
SaveResult::Error(e) => { }
std::fs::remove_file(path).ok(); SaveResult::Error(e) => {
error = Some(format!("Error: {:?}", e)); std::fs::remove_file(path).ok();
return; error = Some(format!("Error: {:?}", e));
} return;
}; }
};
let mut attachment = Attachment::new(file_name, cipher.uuid.clone(), name, size); let mut attachment = Attachment::new(file_name, cipher.uuid.clone(), name, size);
attachment.akey = attachment_key.clone(); attachment.akey = attachment_key.clone();
@ -984,12 +977,22 @@ fn delete_cipher_selected_admin(data: JsonUpcase<Value>, headers: Headers, conn:
} }
#[post("/ciphers/delete-admin", data = "<data>")] #[post("/ciphers/delete-admin", data = "<data>")]
fn delete_cipher_selected_post_admin(data: JsonUpcase<Value>, headers: Headers, conn: DbConn, nt: Notify) -> EmptyResult { fn delete_cipher_selected_post_admin(
data: JsonUpcase<Value>,
headers: Headers,
conn: DbConn,
nt: Notify,
) -> EmptyResult {
delete_cipher_selected_post(data, headers, conn, nt) delete_cipher_selected_post(data, headers, conn, nt)
} }
#[put("/ciphers/delete-admin", data = "<data>")] #[put("/ciphers/delete-admin", data = "<data>")]
fn delete_cipher_selected_put_admin(data: JsonUpcase<Value>, headers: Headers, conn: DbConn, nt: Notify) -> EmptyResult { fn delete_cipher_selected_put_admin(
data: JsonUpcase<Value>,
headers: Headers,
conn: DbConn,
nt: Notify,
) -> EmptyResult {
delete_cipher_selected_put(data, headers, conn, nt) delete_cipher_selected_put(data, headers, conn, nt)
} }
@ -1140,7 +1143,13 @@ fn _delete_cipher_by_uuid(uuid: &str, headers: &Headers, conn: &DbConn, soft_del
Ok(()) Ok(())
} }
fn _delete_multiple_ciphers(data: JsonUpcase<Value>, headers: Headers, conn: DbConn, soft_delete: bool, nt: Notify) -> EmptyResult { fn _delete_multiple_ciphers(
data: JsonUpcase<Value>,
headers: Headers,
conn: DbConn,
soft_delete: bool,
nt: Notify,
) -> EmptyResult {
let data: Value = data.into_inner().data; let data: Value = data.into_inner().data;
let uuids = match data.get("Ids") { let uuids = match data.get("Ids") {
@ -1192,7 +1201,7 @@ fn _restore_multiple_ciphers(data: JsonUpcase<Value>, headers: &Headers, conn: &
for uuid in uuids { for uuid in uuids {
match _restore_cipher_by_uuid(uuid, headers, conn, nt) { match _restore_cipher_by_uuid(uuid, headers, conn, nt) {
Ok(json) => ciphers.push(json.into_inner()), Ok(json) => ciphers.push(json.into_inner()),
err => return err err => return err,
} }
} }

@ -8,15 +8,7 @@ use crate::{
}; };
pub fn routes() -> Vec<rocket::Route> { pub fn routes() -> Vec<rocket::Route> {
routes![ routes![get_folders, get_folder, post_folders, post_folder, put_folder, delete_folder_post, delete_folder,]
get_folders,
get_folder,
post_folders,
post_folder,
put_folder,
delete_folder_post,
delete_folder,
]
} }
#[get("/folders")] #[get("/folders")]

@ -2,20 +2,15 @@ mod accounts;
mod ciphers; mod ciphers;
mod folders; mod folders;
mod organizations; mod organizations;
pub mod two_factor;
mod sends; mod sends;
pub mod two_factor;
pub use sends::start_send_deletion_scheduler; pub use ciphers::purge_trashed_ciphers;
pub use sends::purge_sends;
pub fn routes() -> Vec<Route> { pub fn routes() -> Vec<Route> {
let mut mod_routes = routes![ let mut mod_routes =
clear_device_token, routes![clear_device_token, put_device_token, get_eq_domains, post_eq_domains, put_eq_domains, hibp_breach,];
put_device_token,
get_eq_domains,
post_eq_domains,
put_eq_domains,
hibp_breach,
];
let mut routes = Vec::new(); let mut routes = Vec::new();
routes.append(&mut accounts::routes()); routes.append(&mut accounts::routes());
@ -32,9 +27,9 @@ pub fn routes() -> Vec<Route> {
// //
// Move this somewhere else // Move this somewhere else
// //
use rocket::response::Response;
use rocket::Route; use rocket::Route;
use rocket_contrib::json::Json; use rocket_contrib::json::Json;
use rocket::response::Response;
use serde_json::Value; use serde_json::Value;
use crate::{ use crate::{
@ -42,6 +37,7 @@ use crate::{
auth::Headers, auth::Headers,
db::DbConn, db::DbConn,
error::Error, error::Error,
util::get_reqwest_client,
}; };
#[put("/devices/identifier/<uuid>/clear-token")] #[put("/devices/identifier/<uuid>/clear-token")]
@ -146,22 +142,15 @@ fn put_eq_domains(data: JsonUpcase<EquivDomainData>, headers: Headers, conn: DbC
#[get("/hibp/breach?<username>")] #[get("/hibp/breach?<username>")]
fn hibp_breach(username: String) -> JsonResult { fn hibp_breach(username: String) -> JsonResult {
let user_agent = "Bitwarden_RS";
let url = format!( let url = format!(
"https://haveibeenpwned.com/api/v3/breachedaccount/{}?truncateResponse=false&includeUnverified=false", "https://haveibeenpwned.com/api/v3/breachedaccount/{}?truncateResponse=false&includeUnverified=false",
username username
); );
use reqwest::{blocking::Client, header::USER_AGENT};
if let Some(api_key) = crate::CONFIG.hibp_api_key() { if let Some(api_key) = crate::CONFIG.hibp_api_key() {
let hibp_client = Client::builder().build()?; let hibp_client = get_reqwest_client();
let res = hibp_client let res = hibp_client.get(&url).header("hibp-api-key", api_key).send()?;
.get(&url)
.header(USER_AGENT, user_agent)
.header("hibp-api-key", api_key)
.send()?;
// If we get a 404, return a 404, it means no breached accounts // If we get a 404, return a 404, it means no breached accounts
if res.status() == 404 { if res.status() == 404 {

@ -5,7 +5,7 @@ use serde_json::Value;
use crate::{ use crate::{
api::{EmptyResult, JsonResult, JsonUpcase, JsonUpcaseVec, Notify, NumberOrString, PasswordData, UpdateType}, api::{EmptyResult, JsonResult, JsonUpcase, JsonUpcaseVec, Notify, NumberOrString, PasswordData, UpdateType},
auth::{decode_invite, AdminHeaders, Headers, OwnerHeaders, ManagerHeaders, ManagerHeadersLoose}, auth::{decode_invite, AdminHeaders, Headers, ManagerHeaders, ManagerHeadersLoose, OwnerHeaders},
db::{models::*, DbConn}, db::{models::*, DbConn},
mail, CONFIG, mail, CONFIG,
}; };
@ -333,7 +333,12 @@ fn post_organization_collection_delete_user(
} }
#[delete("/organizations/<org_id>/collections/<col_id>")] #[delete("/organizations/<org_id>/collections/<col_id>")]
fn delete_organization_collection(org_id: String, col_id: String, _headers: ManagerHeaders, conn: DbConn) -> EmptyResult { fn delete_organization_collection(
org_id: String,
col_id: String,
_headers: ManagerHeaders,
conn: DbConn,
) -> EmptyResult {
match Collection::find_by_uuid(&col_id, &conn) { match Collection::find_by_uuid(&col_id, &conn) {
None => err!("Collection not found"), None => err!("Collection not found"),
Some(collection) => { Some(collection) => {
@ -426,9 +431,7 @@ fn put_collection_users(
continue; continue;
} }
CollectionUser::save(&user.user_uuid, &coll_id, CollectionUser::save(&user.user_uuid, &coll_id, d.ReadOnly, d.HidePasswords, &conn)?;
d.ReadOnly, d.HidePasswords,
&conn)?;
} }
Ok(()) Ok(())
@ -443,10 +446,8 @@ struct OrgIdData {
#[get("/ciphers/organization-details?<data..>")] #[get("/ciphers/organization-details?<data..>")]
fn get_org_details(data: Form<OrgIdData>, headers: Headers, conn: DbConn) -> Json<Value> { fn get_org_details(data: Form<OrgIdData>, headers: Headers, conn: DbConn) -> Json<Value> {
let ciphers = Cipher::find_by_org(&data.organization_id, &conn); let ciphers = Cipher::find_by_org(&data.organization_id, &conn);
let ciphers_json: Vec<Value> = ciphers let ciphers_json: Vec<Value> =
.iter() ciphers.iter().map(|c| c.to_json(&headers.host, &headers.user.uuid, &conn)).collect();
.map(|c| c.to_json(&headers.host, &headers.user.uuid, &conn))
.collect();
Json(json!({ Json(json!({
"Data": ciphers_json, "Data": ciphers_json,
@ -544,9 +545,7 @@ fn send_invite(org_id: String, data: JsonUpcase<InviteData>, headers: AdminHeade
match Collection::find_by_uuid_and_org(&col.Id, &org_id, &conn) { match Collection::find_by_uuid_and_org(&col.Id, &org_id, &conn) {
None => err!("Collection not found in Organization"), None => err!("Collection not found in Organization"),
Some(collection) => { Some(collection) => {
CollectionUser::save(&user.uuid, &collection.uuid, CollectionUser::save(&user.uuid, &collection.uuid, col.ReadOnly, col.HidePasswords, &conn)?;
col.ReadOnly, col.HidePasswords,
&conn)?;
} }
} }
} }
@ -801,9 +800,13 @@ fn edit_user(
match Collection::find_by_uuid_and_org(&col.Id, &org_id, &conn) { match Collection::find_by_uuid_and_org(&col.Id, &org_id, &conn) {
None => err!("Collection not found in Organization"), None => err!("Collection not found in Organization"),
Some(collection) => { Some(collection) => {
CollectionUser::save(&user_to_edit.user_uuid, &collection.uuid, CollectionUser::save(
col.ReadOnly, col.HidePasswords, &user_to_edit.user_uuid,
&conn)?; &collection.uuid,
col.ReadOnly,
col.HidePasswords,
&conn,
)?;
} }
} }
} }
@ -899,16 +902,8 @@ fn post_org_import(
.into_iter() .into_iter()
.map(|cipher_data| { .map(|cipher_data| {
let mut cipher = Cipher::new(cipher_data.Type, cipher_data.Name.clone()); let mut cipher = Cipher::new(cipher_data.Type, cipher_data.Name.clone());
update_cipher_from_data( update_cipher_from_data(&mut cipher, cipher_data, &headers, false, &conn, &nt, UpdateType::CipherCreate)
&mut cipher, .ok();
cipher_data,
&headers,
false,
&conn,
&nt,
UpdateType::CipherCreate,
)
.ok();
cipher cipher
}) })
.collect(); .collect();
@ -989,7 +984,13 @@ struct PolicyData {
} }
#[put("/organizations/<org_id>/policies/<pol_type>", data = "<data>")] #[put("/organizations/<org_id>/policies/<pol_type>", data = "<data>")]
fn put_policy(org_id: String, pol_type: i32, data: Json<PolicyData>, _headers: AdminHeaders, conn: DbConn) -> JsonResult { fn put_policy(
org_id: String,
pol_type: i32,
data: Json<PolicyData>,
_headers: AdminHeaders,
conn: DbConn,
) -> JsonResult {
let data: PolicyData = data.into_inner(); let data: PolicyData = data.into_inner();
let pol_type_enum = match OrgPolicyType::from_i32(pol_type) { let pol_type_enum = match OrgPolicyType::from_i32(pol_type) {
@ -1127,8 +1128,7 @@ fn import(org_id: String, data: JsonUpcase<OrgImportData>, headers: Headers, con
// If user is not part of the organization, but it exists // If user is not part of the organization, but it exists
} else if UserOrganization::find_by_email_and_org(&user_data.Email, &org_id, &conn).is_none() { } else if UserOrganization::find_by_email_and_org(&user_data.Email, &org_id, &conn).is_none() {
if let Some (user) = User::find_by_mail(&user_data.Email, &conn) { if let Some(user) = User::find_by_mail(&user_data.Email, &conn) {
let user_org_status = if CONFIG.mail_enabled() { let user_org_status = if CONFIG.mail_enabled() {
UserOrgStatus::Invited as i32 UserOrgStatus::Invited as i32
} else { } else {
@ -1164,7 +1164,7 @@ fn import(org_id: String, data: JsonUpcase<OrgImportData>, headers: Headers, con
// If this flag is enabled, any user that isn't provided in the Users list will be removed (by default they will be kept unless they have Deleted == true) // If this flag is enabled, any user that isn't provided in the Users list will be removed (by default they will be kept unless they have Deleted == true)
if data.OverwriteExisting { if data.OverwriteExisting {
for user_org in UserOrganization::find_by_org_and_type(&org_id, UserOrgType::User as i32, &conn) { for user_org in UserOrganization::find_by_org_and_type(&org_id, UserOrgType::User as i32, &conn) {
if let Some (user_email) = User::find_by_uuid(&user_org.user_uuid, &conn).map(|u| u.email) { if let Some(user_email) = User::find_by_uuid(&user_org.user_uuid, &conn).map(|u| u.email) {
if !data.Users.iter().any(|u| u.Email == user_email) { if !data.Users.iter().any(|u| u.Email == user_email) {
user_org.delete(&conn)?; user_org.delete(&conn)?;
} }

@ -9,39 +9,23 @@ use serde_json::Value;
use crate::{ use crate::{
api::{ApiResult, EmptyResult, JsonResult, JsonUpcase, Notify, UpdateType}, api::{ApiResult, EmptyResult, JsonResult, JsonUpcase, Notify, UpdateType},
auth::{Headers, Host}, auth::{Headers, Host},
db::{models::*, DbConn}, db::{models::*, DbConn, DbPool},
CONFIG, CONFIG,
}; };
const SEND_INACCESSIBLE_MSG: &str = "Send does not exist or is no longer available"; const SEND_INACCESSIBLE_MSG: &str = "Send does not exist or is no longer available";
pub fn routes() -> Vec<rocket::Route> { pub fn routes() -> Vec<rocket::Route> {
routes![ routes![post_send, post_send_file, post_access, post_access_file, put_send, delete_send, put_remove_password]
post_send,
post_send_file,
post_access,
post_access_file,
put_send,
delete_send,
put_remove_password
]
} }
pub fn start_send_deletion_scheduler(pool: crate::db::DbPool) { pub fn purge_sends(pool: DbPool) {
std::thread::spawn(move || { debug!("Purging sends");
loop { if let Ok(conn) = pool.get() {
if let Ok(conn) = pool.get() { Send::purge(&conn);
info!("Initiating send deletion"); } else {
for send in Send::find_all(&conn) { error!("Failed to get DB connection while purging sends")
if chrono::Utc::now().naive_utc() >= send.deletion_date { }
send.delete(&conn).ok();
}
}
}
std::thread::sleep(std::time::Duration::from_secs(3600));
}
});
} }
#[derive(Deserialize)] #[derive(Deserialize)]
@ -179,13 +163,7 @@ fn post_send_file(data: Data, content_type: &ContentType, headers: Headers, conn
None => err!("No model entry present"), None => err!("No model entry present"),
}; };
let size = match data_entry let size = match data_entry.data.save().memory_threshold(0).size_limit(size_limit).with_path(&file_path) {
.data
.save()
.memory_threshold(0)
.size_limit(size_limit)
.with_path(&file_path)
{
SaveResult::Full(SavedData::File(_, size)) => size as i32, SaveResult::Full(SavedData::File(_, size)) => size as i32,
SaveResult::Full(other) => { SaveResult::Full(other) => {
std::fs::remove_file(&file_path).ok(); std::fs::remove_file(&file_path).ok();
@ -206,10 +184,7 @@ fn post_send_file(data: Data, content_type: &ContentType, headers: Headers, conn
if let Some(o) = data_value.as_object_mut() { if let Some(o) = data_value.as_object_mut() {
o.insert(String::from("Id"), Value::String(file_id)); o.insert(String::from("Id"), Value::String(file_id));
o.insert(String::from("Size"), Value::Number(size.into())); o.insert(String::from("Size"), Value::Number(size.into()));
o.insert( o.insert(String::from("SizeName"), Value::String(crate::util::get_display_size(size)));
String::from("SizeName"),
Value::String(crate::util::get_display_size(size)),
);
} }
send.data = serde_json::to_string(&data_value)?; send.data = serde_json::to_string(&data_value)?;

@ -17,11 +17,7 @@ use crate::{
pub use crate::config::CONFIG; pub use crate::config::CONFIG;
pub fn routes() -> Vec<Route> { pub fn routes() -> Vec<Route> {
routes![ routes![generate_authenticator, activate_authenticator, activate_authenticator_put,]
generate_authenticator,
activate_authenticator,
activate_authenticator_put,
]
} }
#[post("/two-factor/get-authenticator", data = "<data>")] #[post("/two-factor/get-authenticator", data = "<data>")]
@ -141,7 +137,7 @@ pub fn validate_totp_code(user_uuid: &str, totp_code: u64, secret: &str, ip: &Cl
// The amount of steps back and forward in time // The amount of steps back and forward in time
// Also check if we need to disable time drifted TOTP codes. // Also check if we need to disable time drifted TOTP codes.
// If that is the case, we set the steps to 0 so only the current TOTP is valid. // If that is the case, we set the steps to 0 so only the current TOTP is valid.
let steps: i64 = if CONFIG.authenticator_disable_time_drift() { 0 } else { 1 }; let steps = !CONFIG.authenticator_disable_time_drift() as i64;
for step in -steps..=steps { for step in -steps..=steps {
let time_step = current_timestamp / 30i64 + step; let time_step = current_timestamp / 30i64 + step;
@ -163,22 +159,11 @@ pub fn validate_totp_code(user_uuid: &str, totp_code: u64, secret: &str, ip: &Cl
twofactor.save(&conn)?; twofactor.save(&conn)?;
return Ok(()); return Ok(());
} else if generated == totp_code && time_step <= twofactor.last_used as i64 { } else if generated == totp_code && time_step <= twofactor.last_used as i64 {
warn!( warn!("This or a TOTP code within {} steps back and forward has already been used!", steps);
"This or a TOTP code within {} steps back and forward has already been used!", err!(format!("Invalid TOTP code! Server time: {} IP: {}", current_time.format("%F %T UTC"), ip.ip));
steps
);
err!(format!(
"Invalid TOTP code! Server time: {} IP: {}",
current_time.format("%F %T UTC"),
ip.ip
));
} }
} }
// Else no valide code received, deny access // Else no valide code received, deny access
err!(format!( err!(format!("Invalid TOTP code! Server time: {} IP: {}", current_time.format("%F %T UTC"), ip.ip));
"Invalid TOTP code! Server time: {} IP: {}",
current_time.format("%F %T UTC"),
ip.ip
));
} }

@ -12,6 +12,7 @@ use crate::{
DbConn, DbConn,
}, },
error::MapResult, error::MapResult,
util::get_reqwest_client,
CONFIG, CONFIG,
}; };
@ -59,7 +60,11 @@ impl DuoData {
ik.replace_range(digits.., replaced); ik.replace_range(digits.., replaced);
sk.replace_range(digits.., replaced); sk.replace_range(digits.., replaced);
Self { host, ik, sk } Self {
host,
ik,
sk,
}
} }
} }
@ -185,9 +190,7 @@ fn activate_duo_put(data: JsonUpcase<EnableDuoData>, headers: Headers, conn: DbC
} }
fn duo_api_request(method: &str, path: &str, params: &str, data: &DuoData) -> EmptyResult { fn duo_api_request(method: &str, path: &str, params: &str, data: &DuoData) -> EmptyResult {
const AGENT: &str = "bitwarden_rs:Duo/1.0 (Rust)"; use reqwest::{header, Method};
use reqwest::{blocking::Client, header::*, Method};
use std::str::FromStr; use std::str::FromStr;
// https://duo.com/docs/authapi#api-details // https://duo.com/docs/authapi#api-details
@ -199,11 +202,13 @@ fn duo_api_request(method: &str, path: &str, params: &str, data: &DuoData) -> Em
let m = Method::from_str(method).unwrap_or_default(); let m = Method::from_str(method).unwrap_or_default();
Client::new() let client = get_reqwest_client();
client
.request(m, &url) .request(m, &url)
.basic_auth(username, Some(password)) .basic_auth(username, Some(password))
.header(USER_AGENT, AGENT) .header(header::USER_AGENT, "vaultwarden:Duo/1.0 (Rust)")
.header(DATE, date) .header(header::DATE, date)
.send()? .send()?
.error_for_status()?; .error_for_status()?;

@ -125,11 +125,7 @@ fn send_email(data: JsonUpcase<SendEmailData>, headers: Headers, conn: DbConn) -
let twofactor_data = EmailTokenData::new(data.Email, generated_token); let twofactor_data = EmailTokenData::new(data.Email, generated_token);
// Uses EmailVerificationChallenge as type to show that it's not verified yet. // Uses EmailVerificationChallenge as type to show that it's not verified yet.
let twofactor = TwoFactor::new( let twofactor = TwoFactor::new(user.uuid, TwoFactorType::EmailVerificationChallenge, twofactor_data.to_json());
user.uuid,
TwoFactorType::EmailVerificationChallenge,
twofactor_data.to_json(),
);
twofactor.save(&conn)?; twofactor.save(&conn)?;
mail::send_token(&twofactor_data.email, &twofactor_data.last_token.map_res("Token is empty")?)?; mail::send_token(&twofactor_data.email, &twofactor_data.last_token.map_res("Token is empty")?)?;
@ -186,7 +182,8 @@ fn email(data: JsonUpcase<EmailData>, headers: Headers, conn: DbConn) -> JsonRes
/// Validate the email code when used as TwoFactor token mechanism /// Validate the email code when used as TwoFactor token mechanism
pub fn validate_email_code_str(user_uuid: &str, token: &str, data: &str, conn: &DbConn) -> EmptyResult { pub fn validate_email_code_str(user_uuid: &str, token: &str, data: &str, conn: &DbConn) -> EmptyResult {
let mut email_data = EmailTokenData::from_json(&data)?; let mut email_data = EmailTokenData::from_json(&data)?;
let mut twofactor = TwoFactor::find_by_user_and_type(&user_uuid, TwoFactorType::Email as i32, &conn).map_res("Two factor not found")?; let mut twofactor = TwoFactor::find_by_user_and_type(&user_uuid, TwoFactorType::Email as i32, &conn)
.map_res("Two factor not found")?;
let issued_token = match &email_data.last_token { let issued_token = match &email_data.last_token {
Some(t) => t, Some(t) => t,
_ => err!("No token available"), _ => err!("No token available"),

@ -20,13 +20,7 @@ pub mod u2f;
pub mod yubikey; pub mod yubikey;
pub fn routes() -> Vec<Route> { pub fn routes() -> Vec<Route> {
let mut routes = routes![ let mut routes = routes![get_twofactor, get_recover, recover, disable_twofactor, disable_twofactor_put,];
get_twofactor,
get_recover,
recover,
disable_twofactor,
disable_twofactor_put,
];
routes.append(&mut authenticator::routes()); routes.append(&mut authenticator::routes());
routes.append(&mut duo::routes()); routes.append(&mut duo::routes());

@ -28,13 +28,7 @@ static APP_ID: Lazy<String> = Lazy::new(|| format!("{}/app-id.json", &CONFIG.dom
static U2F: Lazy<U2f> = Lazy::new(|| U2f::new(APP_ID.clone())); static U2F: Lazy<U2f> = Lazy::new(|| U2f::new(APP_ID.clone()));
pub fn routes() -> Vec<Route> { pub fn routes() -> Vec<Route> {
routes![ routes![generate_u2f, generate_u2f_challenge, activate_u2f, activate_u2f_put, delete_u2f,]
generate_u2f,
generate_u2f_challenge,
activate_u2f,
activate_u2f_put,
delete_u2f,
]
} }
#[post("/two-factor/get-u2f", data = "<data>")] #[post("/two-factor/get-u2f", data = "<data>")]
@ -161,10 +155,7 @@ fn activate_u2f(data: JsonUpcase<EnableU2FData>, headers: Headers, conn: DbConn)
let response: RegisterResponseCopy = serde_json::from_str(&data.DeviceResponse)?; let response: RegisterResponseCopy = serde_json::from_str(&data.DeviceResponse)?;
let error_code = response let error_code = response.error_code.clone().map_or("0".into(), NumberOrString::into_string);
.error_code
.clone()
.map_or("0".into(), NumberOrString::into_string);
if error_code != "0" { if error_code != "0" {
err!("Error registering U2F token") err!("Error registering U2F token")
@ -300,20 +291,13 @@ fn _old_parse_registrations(registations: &str) -> Vec<Registration> {
let regs: Vec<Value> = serde_json::from_str(registations).expect("Can't parse Registration data"); let regs: Vec<Value> = serde_json::from_str(registations).expect("Can't parse Registration data");
regs.into_iter() regs.into_iter().map(|r| serde_json::from_value(r).unwrap()).map(|Helper(r)| r).collect()
.map(|r| serde_json::from_value(r).unwrap())
.map(|Helper(r)| r)
.collect()
} }
pub fn generate_u2f_login(user_uuid: &str, conn: &DbConn) -> ApiResult<U2fSignRequest> { pub fn generate_u2f_login(user_uuid: &str, conn: &DbConn) -> ApiResult<U2fSignRequest> {
let challenge = _create_u2f_challenge(user_uuid, TwoFactorType::U2fLoginChallenge, conn); let challenge = _create_u2f_challenge(user_uuid, TwoFactorType::U2fLoginChallenge, conn);
let registrations: Vec<_> = get_u2f_registrations(user_uuid, conn)? let registrations: Vec<_> = get_u2f_registrations(user_uuid, conn)?.1.into_iter().map(|r| r.reg).collect();
.1
.into_iter()
.map(|r| r.reg)
.collect();
if registrations.is_empty() { if registrations.is_empty() {
err!("No U2F devices registered") err!("No U2F devices registered")

@ -12,14 +12,16 @@ use regex::Regex;
use reqwest::{blocking::Client, blocking::Response, header, Url}; use reqwest::{blocking::Client, blocking::Response, header, Url};
use rocket::{http::ContentType, http::Cookie, response::Content, Route}; use rocket::{http::ContentType, http::Cookie, response::Content, Route};
use crate::{error::Error, util::Cached, CONFIG}; use crate::{
error::Error,
util::{get_reqwest_client_builder, Cached},
CONFIG,
};
pub fn routes() -> Vec<Route> { pub fn routes() -> Vec<Route> {
routes![icon] routes![icon]
} }
const ALLOWED_CHARS: &str = "_-.";
static CLIENT: Lazy<Client> = Lazy::new(|| { static CLIENT: Lazy<Client> = Lazy::new(|| {
// Generate the default headers // Generate the default headers
let mut default_headers = header::HeaderMap::new(); let mut default_headers = header::HeaderMap::new();
@ -27,31 +29,47 @@ static CLIENT: Lazy<Client> = Lazy::new(|| {
default_headers.insert(header::ACCEPT_LANGUAGE, header::HeaderValue::from_static("en-US,en;q=0.8")); default_headers.insert(header::ACCEPT_LANGUAGE, header::HeaderValue::from_static("en-US,en;q=0.8"));
default_headers.insert(header::CACHE_CONTROL, header::HeaderValue::from_static("no-cache")); default_headers.insert(header::CACHE_CONTROL, header::HeaderValue::from_static("no-cache"));
default_headers.insert(header::PRAGMA, header::HeaderValue::from_static("no-cache")); default_headers.insert(header::PRAGMA, header::HeaderValue::from_static("no-cache"));
default_headers.insert(header::ACCEPT, header::HeaderValue::from_static("text/html,application/xhtml+xml,application/xml; q=0.9,image/webp,image/apng,*/*;q=0.8")); default_headers.insert(
header::ACCEPT,
header::HeaderValue::from_static(
"text/html,application/xhtml+xml,application/xml; q=0.9,image/webp,image/apng,*/*;q=0.8",
),
);
// Reuse the client between requests // Reuse the client between requests
Client::builder() get_reqwest_client_builder()
.timeout(Duration::from_secs(CONFIG.icon_download_timeout())) .timeout(Duration::from_secs(CONFIG.icon_download_timeout()))
.default_headers(default_headers) .default_headers(default_headers)
.build() .build()
.unwrap() .expect("Failed to build icon client")
}); });
// Build Regex only once since this takes a lot of time. // Build Regex only once since this takes a lot of time.
static ICON_REL_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?i)icon$|apple.*icon").unwrap()); static ICON_REL_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?i)icon$|apple.*icon").unwrap());
static ICON_REL_BLACKLIST: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?i)mask-icon").unwrap());
static ICON_SIZE_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?x)(\d+)\D*(\d+)").unwrap()); static ICON_SIZE_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?x)(\d+)\D*(\d+)").unwrap());
// Special HashMap which holds the user defined Regex to speedup matching the regex. // Special HashMap which holds the user defined Regex to speedup matching the regex.
static ICON_BLACKLIST_REGEX: Lazy<RwLock<HashMap<String, Regex>>> = Lazy::new(|| RwLock::new(HashMap::new())); static ICON_BLACKLIST_REGEX: Lazy<RwLock<HashMap<String, Regex>>> = Lazy::new(|| RwLock::new(HashMap::new()));
#[get("/<domain>/icon.png")] #[get("/<domain>/icon.png")]
fn icon(domain: String) -> Option<Cached<Content<Vec<u8>>>> { fn icon(domain: String) -> Cached<Content<Vec<u8>>> {
const FALLBACK_ICON: &[u8] = include_bytes!("../static/images/fallback-icon.png");
if !is_valid_domain(&domain) { if !is_valid_domain(&domain) {
warn!("Invalid domain: {}", domain); warn!("Invalid domain: {}", domain);
return None; return Cached::ttl(
Content(ContentType::new("image", "png"), FALLBACK_ICON.to_vec()),
CONFIG.icon_cache_negttl(),
);
} }
get_icon(&domain).map(|icon| Cached::ttl(Content(ContentType::new("image", "x-icon"), icon), CONFIG.icon_cache_ttl())) match get_icon(&domain) {
Some((icon, icon_type)) => {
Cached::ttl(Content(ContentType::new("image", icon_type), icon), CONFIG.icon_cache_ttl())
}
_ => Cached::ttl(Content(ContentType::new("image", "png"), FALLBACK_ICON.to_vec()), CONFIG.icon_cache_negttl()),
}
} }
/// Returns if the domain provided is valid or not. /// Returns if the domain provided is valid or not.
@ -59,6 +77,8 @@ fn icon(domain: String) -> Option<Cached<Content<Vec<u8>>>> {
/// This does some manual checks and makes use of Url to do some basic checking. /// This does some manual checks and makes use of Url to do some basic checking.
/// domains can't be larger then 63 characters (not counting multiple subdomains) according to the RFC's, but we limit the total size to 255. /// domains can't be larger then 63 characters (not counting multiple subdomains) according to the RFC's, but we limit the total size to 255.
fn is_valid_domain(domain: &str) -> bool { fn is_valid_domain(domain: &str) -> bool {
const ALLOWED_CHARS: &str = "_-.";
// If parsing the domain fails using Url, it will not work with reqwest. // If parsing the domain fails using Url, it will not work with reqwest.
if let Err(parse_error) = Url::parse(format!("https://{}", domain).as_str()) { if let Err(parse_error) = Url::parse(format!("https://{}", domain).as_str()) {
debug!("Domain parse error: '{}' - {:?}", domain, parse_error); debug!("Domain parse error: '{}' - {:?}", domain, parse_error);
@ -69,7 +89,10 @@ fn is_valid_domain(domain: &str) -> bool {
|| domain.starts_with('-') || domain.starts_with('-')
|| domain.ends_with('-') || domain.ends_with('-')
{ {
debug!("Domain validation error: '{}' is either empty, contains '..', starts with an '.', starts or ends with a '-'", domain); debug!(
"Domain validation error: '{}' is either empty, contains '..', starts with an '.', starts or ends with a '-'",
domain
);
return false; return false;
} else if domain.len() > 255 { } else if domain.len() > 255 {
debug!("Domain validation error: '{}' exceeds 255 characters", domain); debug!("Domain validation error: '{}' exceeds 255 characters", domain);
@ -238,7 +261,7 @@ fn is_domain_blacklisted(domain: &str) -> bool {
is_blacklisted is_blacklisted
} }
fn get_icon(domain: &str) -> Option<Vec<u8>> { fn get_icon(domain: &str) -> Option<(Vec<u8>, String)> {
let path = format!("{}/{}.png", CONFIG.icon_cache_folder(), domain); let path = format!("{}/{}.png", CONFIG.icon_cache_folder(), domain);
// Check for expiration of negatively cached copy // Check for expiration of negatively cached copy
@ -247,7 +270,11 @@ fn get_icon(domain: &str) -> Option<Vec<u8>> {
} }
if let Some(icon) = get_cached_icon(&path) { if let Some(icon) = get_cached_icon(&path) {
return Some(icon); let icon_type = match get_icon_type(&icon) {
Some(x) => x,
_ => "x-icon",
};
return Some((icon, icon_type.to_string()));
} }
if CONFIG.disable_icon_download() { if CONFIG.disable_icon_download() {
@ -256,9 +283,9 @@ fn get_icon(domain: &str) -> Option<Vec<u8>> {
// Get the icon, or None in case of error // Get the icon, or None in case of error
match download_icon(&domain) { match download_icon(&domain) {
Ok(icon) => { Ok((icon, icon_type)) => {
save_icon(&path, &icon); save_icon(&path, &icon);
Some(icon) Some((icon, icon_type.unwrap_or("x-icon").to_string()))
} }
Err(e) => { Err(e) => {
error!("Error downloading icon: {:?}", e); error!("Error downloading icon: {:?}", e);
@ -319,7 +346,6 @@ fn icon_is_expired(path: &str) -> bool {
expired.unwrap_or(true) expired.unwrap_or(true)
} }
#[derive(Debug)]
struct Icon { struct Icon {
priority: u8, priority: u8,
href: String, href: String,
@ -327,12 +353,20 @@ struct Icon {
impl Icon { impl Icon {
const fn new(priority: u8, href: String) -> Self { const fn new(priority: u8, href: String) -> Self {
Self { href, priority } Self {
href,
priority,
}
} }
} }
fn get_favicons_node(node: &std::rc::Rc<markup5ever_rcdom::Node>, icons: &mut Vec<Icon>, url: &Url) { fn get_favicons_node(node: &std::rc::Rc<markup5ever_rcdom::Node>, icons: &mut Vec<Icon>, url: &Url) {
if let markup5ever_rcdom::NodeData::Element { name, attrs, .. } = &node.data { if let markup5ever_rcdom::NodeData::Element {
name,
attrs,
..
} = &node.data
{
if name.local.as_ref() == "link" { if name.local.as_ref() == "link" {
let mut has_rel = false; let mut has_rel = false;
let mut href = None; let mut href = None;
@ -343,7 +377,8 @@ fn get_favicons_node(node: &std::rc::Rc<markup5ever_rcdom::Node>, icons: &mut Ve
let attr_name = attr.name.local.as_ref(); let attr_name = attr.name.local.as_ref();
let attr_value = attr.value.as_ref(); let attr_value = attr.value.as_ref();
if attr_name == "rel" && ICON_REL_REGEX.is_match(attr_value) { if attr_name == "rel" && ICON_REL_REGEX.is_match(attr_value) && !ICON_REL_BLACKLIST.is_match(attr_value)
{
has_rel = true; has_rel = true;
} else if attr_name == "href" { } else if attr_name == "href" {
href = Some(attr_value); href = Some(attr_value);
@ -486,10 +521,10 @@ fn get_icon_url(domain: &str) -> Result<IconUrlResult, Error> {
iconlist.sort_by_key(|x| x.priority); iconlist.sort_by_key(|x| x.priority);
// There always is an icon in the list, so no need to check if it exists, and just return the first one // There always is an icon in the list, so no need to check if it exists, and just return the first one
Ok(IconUrlResult{ Ok(IconUrlResult {
iconlist, iconlist,
cookies: cookie_str, cookies: cookie_str,
referer referer,
}) })
} }
@ -510,9 +545,7 @@ fn get_page_with_cookies(url: &str, cookie_str: &str, referer: &str) -> Result<R
client = client.header("Referer", referer) client = client.header("Referer", referer)
} }
client.send()? client.send()?.error_for_status().map_err(Into::into)
.error_for_status()
.map_err(Into::into)
} }
/// Returns a Integer with the priority of the type of the icon which to prefer. /// Returns a Integer with the priority of the type of the icon which to prefer.
@ -594,7 +627,7 @@ fn parse_sizes(sizes: Option<&str>) -> (u16, u16) {
(width, height) (width, height)
} }
fn download_icon(domain: &str) -> Result<Vec<u8>, Error> { fn download_icon(domain: &str) -> Result<(Vec<u8>, Option<&str>), Error> {
if is_domain_blacklisted(domain) { if is_domain_blacklisted(domain) {
err!("Domain is blacklisted", domain) err!("Domain is blacklisted", domain)
} }
@ -602,6 +635,7 @@ fn download_icon(domain: &str) -> Result<Vec<u8>, Error> {
let icon_result = get_icon_url(&domain)?; let icon_result = get_icon_url(&domain)?;
let mut buffer = Vec::new(); let mut buffer = Vec::new();
let mut icon_type: Option<&str> = None;
use data_url::DataUrl; use data_url::DataUrl;
@ -613,29 +647,43 @@ fn download_icon(domain: &str) -> Result<Vec<u8>, Error> {
Ok((body, _fragment)) => { Ok((body, _fragment)) => {
// Also check if the size is atleast 67 bytes, which seems to be the smallest png i could create // Also check if the size is atleast 67 bytes, which seems to be the smallest png i could create
if body.len() >= 67 { if body.len() >= 67 {
// Check if the icon type is allowed, else try an icon from the list.
icon_type = get_icon_type(&body);
if icon_type.is_none() {
debug!("Icon from {} data:image uri, is not a valid image type", domain);
continue;
}
info!("Extracted icon from data:image uri for {}", domain);
buffer = body; buffer = body;
break; break;
} }
} }
_ => warn!("data uri is invalid"), _ => warn!("Extracted icon from data:image uri is invalid"),
}; };
} else { } else {
match get_page_with_cookies(&icon.href, &icon_result.cookies, &icon_result.referer) { match get_page_with_cookies(&icon.href, &icon_result.cookies, &icon_result.referer) {
Ok(mut res) => { Ok(mut res) => {
info!("Downloaded icon from {}", icon.href);
res.copy_to(&mut buffer)?; res.copy_to(&mut buffer)?;
// Check if the icon type is allowed, else try an icon from the list.
icon_type = get_icon_type(&buffer);
if icon_type.is_none() {
buffer.clear();
debug!("Icon from {}, is not a valid image type", icon.href);
continue;
}
info!("Downloaded icon from {}", icon.href);
break; break;
}, }
_ => warn!("Download failed for {}", icon.href), _ => warn!("Download failed for {}", icon.href),
}; };
} }
} }
if buffer.is_empty() { if buffer.is_empty() {
err!("Empty response") err!("Empty response downloading icon")
} }
Ok(buffer) Ok((buffer, icon_type))
} }
fn save_icon(path: &str, icon: &[u8]) { fn save_icon(path: &str, icon: &[u8]) {
@ -647,7 +695,18 @@ fn save_icon(path: &str, icon: &[u8]) {
create_dir_all(&CONFIG.icon_cache_folder()).expect("Error creating icon cache"); create_dir_all(&CONFIG.icon_cache_folder()).expect("Error creating icon cache");
} }
Err(e) => { Err(e) => {
info!("Icon save error: {:?}", e); warn!("Icon save error: {:?}", e);
} }
} }
} }
fn get_icon_type(bytes: &[u8]) -> Option<&'static str> {
match bytes {
[137, 80, 78, 71, ..] => Some("png"),
[0, 0, 1, 0, ..] => Some("x-icon"),
[82, 73, 70, 70, ..] => Some("webp"),
[255, 216, 255, ..] => Some("jpeg"),
[66, 77, ..] => Some("bmp"),
_ => None,
}
}

@ -72,7 +72,8 @@ fn _refresh_login(data: ConnectData, conn: DbConn) -> JsonResult {
"Kdf": user.client_kdf_type, "Kdf": user.client_kdf_type,
"KdfIterations": user.client_kdf_iter, "KdfIterations": user.client_kdf_iter,
"ResetMasterPassword": false, // TODO: according to official server seems something like: user.password_hash.is_empty(), but would need testing "ResetMasterPassword": false, // TODO: according to official server seems something like: user.password_hash.is_empty(), but would need testing
"scope": "api offline_access" "scope": "api offline_access",
"unofficialServer": true,
}))) })))
} }
@ -87,34 +88,28 @@ fn _password_login(data: ConnectData, conn: DbConn, ip: &ClientIp) -> JsonResult
let username = data.username.as_ref().unwrap(); let username = data.username.as_ref().unwrap();
let user = match User::find_by_mail(username, &conn) { let user = match User::find_by_mail(username, &conn) {
Some(user) => user, Some(user) => user,
None => err!( None => err!("Username or password is incorrect. Try again", format!("IP: {}. Username: {}.", ip.ip, username)),
"Username or password is incorrect. Try again",
format!("IP: {}. Username: {}.", ip.ip, username)
),
}; };
// Check password // Check password
let password = data.password.as_ref().unwrap(); let password = data.password.as_ref().unwrap();
if !user.check_valid_password(password) { if !user.check_valid_password(password) {
err!( err!("Username or password is incorrect. Try again", format!("IP: {}. Username: {}.", ip.ip, username))
"Username or password is incorrect. Try again",
format!("IP: {}. Username: {}.", ip.ip, username)
)
} }
// Check if the user is disabled // Check if the user is disabled
if !user.enabled { if !user.enabled {
err!( err!("This user has been disabled", format!("IP: {}. Username: {}.", ip.ip, username))
"This user has been disabled",
format!("IP: {}. Username: {}.", ip.ip, username)
)
} }
let now = Local::now(); let now = Local::now();
if user.verified_at.is_none() && CONFIG.mail_enabled() && CONFIG.signups_verify() { if user.verified_at.is_none() && CONFIG.mail_enabled() && CONFIG.signups_verify() {
let now = now.naive_utc(); let now = now.naive_utc();
if user.last_verifying_at.is_none() || now.signed_duration_since(user.last_verifying_at.unwrap()).num_seconds() > CONFIG.signups_verify_resend_time() as i64 { if user.last_verifying_at.is_none()
|| now.signed_duration_since(user.last_verifying_at.unwrap()).num_seconds()
> CONFIG.signups_verify_resend_time() as i64
{
let resend_limit = CONFIG.signups_verify_resend_limit() as i32; let resend_limit = CONFIG.signups_verify_resend_limit() as i32;
if resend_limit == 0 || user.login_verify_count < resend_limit { if resend_limit == 0 || user.login_verify_count < resend_limit {
// We want to send another email verification if we require signups to verify // We want to send another email verification if we require signups to verify
@ -134,10 +129,7 @@ fn _password_login(data: ConnectData, conn: DbConn, ip: &ClientIp) -> JsonResult
} }
// We still want the login to fail until they actually verified the email address // We still want the login to fail until they actually verified the email address
err!( err!("Please verify your email before trying again.", format!("IP: {}. Username: {}.", ip.ip, username))
"Please verify your email before trying again.",
format!("IP: {}. Username: {}.", ip.ip, username)
)
} }
let (mut device, new_device) = get_device(&data, &conn, &user); let (mut device, new_device) = get_device(&data, &conn, &user);
@ -172,7 +164,8 @@ fn _password_login(data: ConnectData, conn: DbConn, ip: &ClientIp) -> JsonResult
"Kdf": user.client_kdf_type, "Kdf": user.client_kdf_type,
"KdfIterations": user.client_kdf_iter, "KdfIterations": user.client_kdf_iter,
"ResetMasterPassword": false,// TODO: Same as above "ResetMasterPassword": false,// TODO: Same as above
"scope": "api offline_access" "scope": "api offline_access",
"unofficialServer": true,
}); });
if let Some(token) = twofactor_token { if let Some(token) = twofactor_token {
@ -234,9 +227,7 @@ fn twofactor_auth(
None => err_json!(_json_err_twofactor(&twofactor_ids, user_uuid, conn)?, "2FA token not provided"), None => err_json!(_json_err_twofactor(&twofactor_ids, user_uuid, conn)?, "2FA token not provided"),
}; };
let selected_twofactor = twofactors let selected_twofactor = twofactors.into_iter().find(|tf| tf.atype == selected_id && tf.enabled);
.into_iter()
.find(|tf| tf.atype == selected_id && tf.enabled);
use crate::api::core::two_factor as _tf; use crate::api::core::two_factor as _tf;
use crate::crypto::ct_eq; use crate::crypto::ct_eq;
@ -245,18 +236,26 @@ fn twofactor_auth(
let mut remember = data.two_factor_remember.unwrap_or(0); let mut remember = data.two_factor_remember.unwrap_or(0);
match TwoFactorType::from_i32(selected_id) { match TwoFactorType::from_i32(selected_id) {
Some(TwoFactorType::Authenticator) => _tf::authenticator::validate_totp_code_str(user_uuid, twofactor_code, &selected_data?, ip, conn)?, Some(TwoFactorType::Authenticator) => {
_tf::authenticator::validate_totp_code_str(user_uuid, twofactor_code, &selected_data?, ip, conn)?
}
Some(TwoFactorType::U2f) => _tf::u2f::validate_u2f_login(user_uuid, twofactor_code, conn)?, Some(TwoFactorType::U2f) => _tf::u2f::validate_u2f_login(user_uuid, twofactor_code, conn)?,
Some(TwoFactorType::YubiKey) => _tf::yubikey::validate_yubikey_login(twofactor_code, &selected_data?)?, Some(TwoFactorType::YubiKey) => _tf::yubikey::validate_yubikey_login(twofactor_code, &selected_data?)?,
Some(TwoFactorType::Duo) => _tf::duo::validate_duo_login(data.username.as_ref().unwrap(), twofactor_code, conn)?, Some(TwoFactorType::Duo) => {
Some(TwoFactorType::Email) => _tf::email::validate_email_code_str(user_uuid, twofactor_code, &selected_data?, conn)?, _tf::duo::validate_duo_login(data.username.as_ref().unwrap(), twofactor_code, conn)?
}
Some(TwoFactorType::Email) => {
_tf::email::validate_email_code_str(user_uuid, twofactor_code, &selected_data?, conn)?
}
Some(TwoFactorType::Remember) => { Some(TwoFactorType::Remember) => {
match device.twofactor_remember { match device.twofactor_remember {
Some(ref code) if !CONFIG.disable_2fa_remember() && ct_eq(code, twofactor_code) => { Some(ref code) if !CONFIG.disable_2fa_remember() && ct_eq(code, twofactor_code) => {
remember = 1; // Make sure we also return the token here, otherwise it will only remember the first time remember = 1; // Make sure we also return the token here, otherwise it will only remember the first time
} }
_ => err_json!(_json_err_twofactor(&twofactor_ids, user_uuid, conn)?, "2FA Remember token not provided"), _ => {
err_json!(_json_err_twofactor(&twofactor_ids, user_uuid, conn)?, "2FA Remember token not provided")
}
} }
} }
_ => err!("Invalid two factor provider"), _ => err!("Invalid two factor provider"),

@ -10,8 +10,9 @@ use serde_json::Value;
pub use crate::api::{ pub use crate::api::{
admin::routes as admin_routes, admin::routes as admin_routes,
core::purge_sends,
core::purge_trashed_ciphers,
core::routes as core_routes, core::routes as core_routes,
core::start_send_deletion_scheduler,
icons::routes as icons_routes, icons::routes as icons_routes,
identity::routes as identity_routes, identity::routes as identity_routes,
notifications::routes as notifications_routes, notifications::routes as notifications_routes,
@ -54,9 +55,9 @@ impl NumberOrString {
use std::num::ParseIntError as PIE; use std::num::ParseIntError as PIE;
match self { match self {
NumberOrString::Number(n) => Ok(n), NumberOrString::Number(n) => Ok(n),
NumberOrString::String(s) => s NumberOrString::String(s) => {
.parse() s.parse().map_err(|e: PIE| crate::Error::new("Can't convert to number", e.to_string()))
.map_err(|e: PIE| crate::Error::new("Can't convert to number", e.to_string())), }
} }
} }
} }

@ -4,12 +4,7 @@ use rocket::Route;
use rocket_contrib::json::Json; use rocket_contrib::json::Json;
use serde_json::Value as JsonValue; use serde_json::Value as JsonValue;
use crate::{ use crate::{api::EmptyResult, auth::Headers, db::DbConn, Error, CONFIG};
api::EmptyResult,
auth::Headers,
db::DbConn,
Error, CONFIG,
};
pub fn routes() -> Vec<Route> { pub fn routes() -> Vec<Route> {
routes![negotiate, websockets_err] routes![negotiate, websockets_err]
@ -19,12 +14,16 @@ static SHOW_WEBSOCKETS_MSG: AtomicBool = AtomicBool::new(true);
#[get("/hub")] #[get("/hub")]
fn websockets_err() -> EmptyResult { fn websockets_err() -> EmptyResult {
if CONFIG.websocket_enabled() && SHOW_WEBSOCKETS_MSG.compare_exchange(true, false, Ordering::Relaxed, Ordering::Relaxed).is_ok() { if CONFIG.websocket_enabled()
err!(" && SHOW_WEBSOCKETS_MSG.compare_exchange(true, false, Ordering::Relaxed, Ordering::Relaxed).is_ok()
{
err!(
"
########################################################### ###########################################################
'/notifications/hub' should be proxied to the websocket server or notifications won't work. '/notifications/hub' should be proxied to the websocket server or notifications won't work.
Go to the Wiki for more info, or disable WebSockets setting WEBSOCKET_ENABLED=false. Go to the Wiki for more info, or disable WebSockets setting WEBSOCKET_ENABLED=false.
###########################################################################################\n") ###########################################################################################\n"
)
} else { } else {
Err(Error::empty()) Err(Error::empty())
} }
@ -204,9 +203,7 @@ impl Handler for WsHandler {
let handler_insert = self.out.clone(); let handler_insert = self.out.clone();
let handler_update = self.out.clone(); let handler_update = self.out.clone();
self.users self.users.map.upsert(user_uuid, || vec![handler_insert], |ref mut v| v.push(handler_update));
.map
.upsert(user_uuid, || vec![handler_insert], |ref mut v| v.push(handler_update));
// Schedule a ping to keep the connection alive // Schedule a ping to keep the connection alive
self.out.timeout(PING_MS, PING) self.out.timeout(PING_MS, PING)
@ -216,7 +213,11 @@ impl Handler for WsHandler {
if let Message::Text(text) = msg.clone() { if let Message::Text(text) = msg.clone() {
let json = &text[..text.len() - 1]; // Remove last char let json = &text[..text.len() - 1]; // Remove last char
if let Ok(InitialMessage { protocol, version }) = from_str::<InitialMessage>(json) { if let Ok(InitialMessage {
protocol,
version,
}) = from_str::<InitialMessage>(json)
{
if &protocol == "messagepack" && version == 1 { if &protocol == "messagepack" && version == 1 {
return self.out.send(&INITIAL_RESPONSE[..]); // Respond to initial message return self.out.send(&INITIAL_RESPONSE[..]); // Respond to initial message
} }
@ -295,10 +296,7 @@ impl WebSocketUsers {
// NOTE: The last modified date needs to be updated before calling these methods // NOTE: The last modified date needs to be updated before calling these methods
pub fn send_user_update(&self, ut: UpdateType, user: &User) { pub fn send_user_update(&self, ut: UpdateType, user: &User) {
let data = create_update( let data = create_update(
vec![ vec![("UserId".into(), user.uuid.clone().into()), ("Date".into(), serialize_date(user.updated_at))],
("UserId".into(), user.uuid.clone().into()),
("Date".into(), serialize_date(user.updated_at)),
],
ut, ut,
); );

@ -83,11 +83,15 @@ fn static_files(filename: String) -> Result<Content<&'static [u8]>, Error> {
"hibp.png" => Ok(Content(ContentType::PNG, include_bytes!("../static/images/hibp.png"))), "hibp.png" => Ok(Content(ContentType::PNG, include_bytes!("../static/images/hibp.png"))),
"bootstrap.css" => Ok(Content(ContentType::CSS, include_bytes!("../static/scripts/bootstrap.css"))), "bootstrap.css" => Ok(Content(ContentType::CSS, include_bytes!("../static/scripts/bootstrap.css"))),
"bootstrap-native.js" => Ok(Content(ContentType::JavaScript, include_bytes!("../static/scripts/bootstrap-native.js"))), "bootstrap-native.js" => {
Ok(Content(ContentType::JavaScript, include_bytes!("../static/scripts/bootstrap-native.js")))
}
"identicon.js" => Ok(Content(ContentType::JavaScript, include_bytes!("../static/scripts/identicon.js"))), "identicon.js" => Ok(Content(ContentType::JavaScript, include_bytes!("../static/scripts/identicon.js"))),
"datatables.js" => Ok(Content(ContentType::JavaScript, include_bytes!("../static/scripts/datatables.js"))), "datatables.js" => Ok(Content(ContentType::JavaScript, include_bytes!("../static/scripts/datatables.js"))),
"datatables.css" => Ok(Content(ContentType::CSS, include_bytes!("../static/scripts/datatables.css"))), "datatables.css" => Ok(Content(ContentType::CSS, include_bytes!("../static/scripts/datatables.css"))),
"jquery-3.5.1.slim.js" => Ok(Content(ContentType::JavaScript, include_bytes!("../static/scripts/jquery-3.5.1.slim.js"))), "jquery-3.5.1.slim.js" => {
Ok(Content(ContentType::JavaScript, include_bytes!("../static/scripts/jquery-3.5.1.slim.js")))
}
_ => err!(format!("Static file not found: {}", filename)), _ => err!(format!("Static file not found: {}", filename)),
} }
} }

@ -223,10 +223,9 @@ use crate::db::{
}; };
pub struct Host { pub struct Host {
pub host: String pub host: String,
} }
impl<'a, 'r> FromRequest<'a, 'r> for Host { impl<'a, 'r> FromRequest<'a, 'r> for Host {
type Error = &'static str; type Error = &'static str;
@ -261,7 +260,9 @@ impl<'a, 'r> FromRequest<'a, 'r> for Host {
format!("{}://{}", protocol, host) format!("{}://{}", protocol, host)
}; };
Outcome::Success(Host { host }) Outcome::Success(Host {
host,
})
} }
} }
@ -317,10 +318,8 @@ impl<'a, 'r> FromRequest<'a, 'r> for Headers {
}; };
if user.security_stamp != claims.sstamp { if user.security_stamp != claims.sstamp {
if let Some(stamp_exception) = user if let Some(stamp_exception) =
.stamp_exception user.stamp_exception.as_deref().and_then(|s| serde_json::from_str::<UserStampException>(s).ok())
.as_deref()
.and_then(|s| serde_json::from_str::<UserStampException>(s).ok())
{ {
let current_route = match request.route().and_then(|r| r.name) { let current_route = match request.route().and_then(|r| r.name) {
Some(name) => name, Some(name) => name,
@ -338,7 +337,11 @@ impl<'a, 'r> FromRequest<'a, 'r> for Headers {
} }
} }
Outcome::Success(Headers { host, device, user }) Outcome::Success(Headers {
host,
device,
user,
})
} }
} }
@ -506,7 +509,11 @@ impl<'a, 'r> FromRequest<'a, 'r> for ManagerHeaders {
}; };
if !headers.org_user.has_full_access() { if !headers.org_user.has_full_access() {
match CollectionUser::find_by_collection_and_user(&col_id, &headers.org_user.user_uuid, &conn) { match CollectionUser::find_by_collection_and_user(
&col_id,
&headers.org_user.user_uuid,
&conn,
) {
Some(_) => (), Some(_) => (),
None => err_handler!("The current user isn't a manager for this collection"), None => err_handler!("The current user isn't a manager for this collection"),
} }
@ -636,10 +643,10 @@ impl<'a, 'r> FromRequest<'a, 'r> for ClientIp {
None None
}; };
let ip = ip let ip = ip.or_else(|| req.remote().map(|r| r.ip())).unwrap_or_else(|| "0.0.0.0".parse().unwrap());
.or_else(|| req.remote().map(|r| r.ip()))
.unwrap_or_else(|| "0.0.0.0".parse().unwrap());
Outcome::Success(ClientIp { ip }) Outcome::Success(ClientIp {
ip,
})
} }
} }

@ -316,6 +316,17 @@ make_config! {
/// Websocket port /// Websocket port
websocket_port: u16, false, def, 3012; websocket_port: u16, false, def, 3012;
}, },
jobs {
/// Job scheduler poll interval |> How often the job scheduler thread checks for jobs to run.
/// Set to 0 to globally disable scheduled jobs.
job_poll_interval_ms: u64, false, def, 30_000;
/// Send purge schedule |> Cron schedule of the job that checks for Sends past their deletion date.
/// Defaults to hourly. Set blank to disable this job.
send_purge_schedule: String, false, def, "0 5 * * * *".to_string();
/// Trash purge schedule |> Cron schedule of the job that checks for trashed items to delete permanently.
/// Defaults to daily. Set blank to disable this job.
trash_purge_schedule: String, false, def, "0 5 0 * * *".to_string();
},
/// General settings /// General settings
settings { settings {
@ -339,11 +350,16 @@ make_config! {
/// Per-organization attachment limit (KB) |> Limit in kilobytes for an organization attachments, once the limit is exceeded it won't be possible to upload more /// Per-organization attachment limit (KB) |> Limit in kilobytes for an organization attachments, once the limit is exceeded it won't be possible to upload more
org_attachment_limit: i64, true, option; org_attachment_limit: i64, true, option;
/// Trash auto-delete days |> Number of days to wait before auto-deleting a trashed item.
/// If unset, trashed items are not auto-deleted. This setting applies globally, so make
/// sure to inform all users of any changes to this setting.
trash_auto_delete_days: i64, true, option;
/// Disable icon downloads |> Set to true to disable icon downloading, this would still serve icons from /// Disable icon downloads |> Set to true to disable icon downloading, this would still serve icons from
/// $ICON_CACHE_FOLDER, but it won't produce any external network request. Needs to set $ICON_CACHE_TTL to 0, /// $ICON_CACHE_FOLDER, but it won't produce any external network request. Needs to set $ICON_CACHE_TTL to 0,
/// otherwise it will delete them and they won't be downloaded again. /// otherwise it will delete them and they won't be downloaded again.
disable_icon_download: bool, true, def, false; disable_icon_download: bool, true, def, false;
/// Allow new signups |> Controls whether new users can register. Users can be invited by the bitwarden_rs admin even if this is disabled /// Allow new signups |> Controls whether new users can register. Users can be invited by the vaultwarden admin even if this is disabled
signups_allowed: bool, true, def, true; signups_allowed: bool, true, def, true;
/// Require email verification on signups. This will prevent logins from succeeding until the address has been verified /// Require email verification on signups. This will prevent logins from succeeding until the address has been verified
signups_verify: bool, true, def, false; signups_verify: bool, true, def, false;
@ -369,7 +385,7 @@ make_config! {
admin_token: Pass, true, option; admin_token: Pass, true, option;
/// Invitation organization name |> Name shown in the invitation emails that don't come from a specific organization /// Invitation organization name |> Name shown in the invitation emails that don't come from a specific organization
invitation_org_name: String, true, def, "Bitwarden_RS".to_string(); invitation_org_name: String, true, def, "Vaultwarden".to_string();
}, },
/// Advanced settings /// Advanced settings
@ -418,7 +434,7 @@ make_config! {
/// Log level /// Log level
log_level: String, false, def, "Info".to_string(); log_level: String, false, def, "Info".to_string();
/// Enable DB WAL |> Turning this off might lead to worse performance, but might help if using bitwarden_rs on some exotic filesystems, /// Enable DB WAL |> Turning this off might lead to worse performance, but might help if using vaultwarden on some exotic filesystems,
/// that do not support WAL. Please make sure you read project wiki on the topic before changing this setting. /// that do not support WAL. Please make sure you read project wiki on the topic before changing this setting.
enable_db_wal: bool, false, def, true; enable_db_wal: bool, false, def, true;
@ -473,7 +489,7 @@ make_config! {
/// From Address /// From Address
smtp_from: String, true, def, String::new(); smtp_from: String, true, def, String::new();
/// From Name /// From Name
smtp_from_name: String, true, def, "Bitwarden_RS".to_string(); smtp_from_name: String, true, def, "Vaultwarden".to_string();
/// Username /// Username
smtp_username: String, true, option; smtp_username: String, true, option;
/// Password /// Password
@ -511,10 +527,7 @@ fn validate_config(cfg: &ConfigItems) -> Result<(), Error> {
let limit = 256; let limit = 256;
if cfg.database_max_conns < 1 || cfg.database_max_conns > limit { if cfg.database_max_conns < 1 || cfg.database_max_conns > limit {
err!(format!( err!(format!("`DATABASE_MAX_CONNS` contains an invalid value. Ensure it is between 1 and {}.", limit,));
"`DATABASE_MAX_CONNS` contains an invalid value. Ensure it is between 1 and {}.",
limit,
));
} }
let dom = cfg.domain.to_lowercase(); let dom = cfg.domain.to_lowercase();
@ -855,9 +868,7 @@ fn case_helper<'reg, 'rc>(
rc: &mut RenderContext<'reg, 'rc>, rc: &mut RenderContext<'reg, 'rc>,
out: &mut dyn Output, out: &mut dyn Output,
) -> HelperResult { ) -> HelperResult {
let param = h let param = h.param(0).ok_or_else(|| RenderError::new("Param not found for helper \"case\""))?;
.param(0)
.ok_or_else(|| RenderError::new("Param not found for helper \"case\""))?;
let value = param.value().clone(); let value = param.value().clone();
if h.params().iter().skip(1).any(|x| x.value() == &value) { if h.params().iter().skip(1).any(|x| x.value() == &value) {
@ -874,21 +885,15 @@ fn js_escape_helper<'reg, 'rc>(
_rc: &mut RenderContext<'reg, 'rc>, _rc: &mut RenderContext<'reg, 'rc>,
out: &mut dyn Output, out: &mut dyn Output,
) -> HelperResult { ) -> HelperResult {
let param = h let param = h.param(0).ok_or_else(|| RenderError::new("Param not found for helper \"js_escape\""))?;
.param(0)
.ok_or_else(|| RenderError::new("Param not found for helper \"js_escape\""))?;
let no_quote = h let no_quote = h.param(1).is_some();
.param(1)
.is_some();
let value = param let value =
.value() param.value().as_str().ok_or_else(|| RenderError::new("Param for helper \"js_escape\" is not a String"))?;
.as_str()
.ok_or_else(|| RenderError::new("Param for helper \"js_escape\" is not a String"))?;
let mut escaped_value = value.replace('\\', "").replace('\'', "\\x22").replace('\"', "\\x27"); let mut escaped_value = value.replace('\\', "").replace('\'', "\\x22").replace('\"', "\\x27");
if ! no_quote { if !no_quote {
escaped_value = format!("&quot;{}&quot;", escaped_value); escaped_value = format!("&quot;{}&quot;", escaped_value);
} }

@ -47,9 +47,7 @@ pub fn get_random_64() -> Vec<u8> {
pub fn get_random(mut array: Vec<u8>) -> Vec<u8> { pub fn get_random(mut array: Vec<u8>) -> Vec<u8> {
use ring::rand::{SecureRandom, SystemRandom}; use ring::rand::{SecureRandom, SystemRandom};
SystemRandom::new() SystemRandom::new().fill(&mut array).expect("Error generating random values");
.fill(&mut array)
.expect("Error generating random values");
array array
} }

@ -1,5 +1,3 @@
use std::process::Command;
use chrono::prelude::*; use chrono::prelude::*;
use diesel::r2d2::{ConnectionManager, Pool, PooledConnection}; use diesel::r2d2::{ConnectionManager, Pool, PooledConnection};
use rocket::{ use rocket::{
@ -25,7 +23,6 @@ pub mod __mysql_schema;
#[path = "schemas/postgresql/schema.rs"] #[path = "schemas/postgresql/schema.rs"]
pub mod __postgresql_schema; pub mod __postgresql_schema;
// This is used to generate the main DbConn and DbPool enums, which contain one variant for each database supported // This is used to generate the main DbConn and DbPool enums, which contain one variant for each database supported
macro_rules! generate_connections { macro_rules! generate_connections {
( $( $name:ident: $ty:ty ),+ ) => { ( $( $name:ident: $ty:ty ),+ ) => {
@ -110,7 +107,6 @@ impl DbConnType {
} }
} }
#[macro_export] #[macro_export]
macro_rules! db_run { macro_rules! db_run {
// Same for all dbs // Same for all dbs
@ -144,6 +140,7 @@ macro_rules! db_run {
// Different code for each db // Different code for each db
( @raw $conn:ident: $( $($db:ident),+ $body:block )+ ) => { ( @raw $conn:ident: $( $($db:ident),+ $body:block )+ ) => {
#[allow(unused)] use diesel::prelude::*; #[allow(unused)] use diesel::prelude::*;
#[allow(unused_variables)]
match $conn { match $conn {
$($( $($(
#[cfg($db)] #[cfg($db)]
@ -155,7 +152,6 @@ macro_rules! db_run {
}; };
} }
pub trait FromDb { pub trait FromDb {
type Output; type Output;
#[allow(clippy::wrong_self_convention)] #[allow(clippy::wrong_self_convention)]
@ -221,51 +217,35 @@ macro_rules! db_object {
// Reexport the models, needs to be after the macros are defined so it can access them // Reexport the models, needs to be after the macros are defined so it can access them
pub mod models; pub mod models;
/// Creates a back-up of the database using sqlite3 /// Creates a back-up of the sqlite database
pub fn backup_database() -> Result<(), Error> { /// MySQL/MariaDB and PostgreSQL are not supported.
use std::path::Path; pub fn backup_database(conn: &DbConn) -> Result<(), Error> {
let db_url = CONFIG.database_url(); db_run! {@raw conn:
let db_path = Path::new(&db_url).parent().unwrap(); postgresql, mysql {
err!("PostgreSQL and MySQL/MariaDB do not support this backup feature");
let now: DateTime<Utc> = Utc::now(); }
let file_date = now.format("%Y%m%d").to_string(); sqlite {
let backup_command: String = format!("{}{}{}", ".backup 'db_", file_date, ".sqlite3'"); use std::path::Path;
let db_url = CONFIG.database_url();
Command::new("sqlite3") let db_path = Path::new(&db_url).parent().unwrap().to_string_lossy();
.current_dir(db_path) let file_date = Utc::now().format("%Y%m%d_%H%M%S").to_string();
.args(&["db.sqlite3", &backup_command]) diesel::sql_query(format!("VACUUM INTO '{}/db_{}.sqlite3'", db_path, file_date)).execute(conn)?;
.output() }
.expect("Can't open database, sqlite3 is not available, make sure it's installed and available on the PATH"); }
Ok(()) Ok(())
} }
use diesel::sql_types::Text;
#[derive(QueryableByName,Debug)]
struct SqlVersion {
#[sql_type = "Text"]
version: String,
}
/// Get the SQL Server version /// Get the SQL Server version
pub fn get_sql_server_version(conn: &DbConn) -> String { pub fn get_sql_server_version(conn: &DbConn) -> String {
db_run! {@raw conn: db_run! {@raw conn:
postgresql, mysql { postgresql, mysql {
match diesel::sql_query("SELECT version() AS version;").get_result::<SqlVersion>(conn).ok() { no_arg_sql_function!(version, diesel::sql_types::Text);
Some(v) => { diesel::select(version).get_result::<String>(conn).unwrap_or_else(|_| "Unknown".to_string())
v.version
},
_ => "Unknown".to_string()
}
} }
sqlite { sqlite {
match diesel::sql_query("SELECT sqlite_version() AS version;").get_result::<SqlVersion>(conn).ok() { no_arg_sql_function!(sqlite_version, diesel::sql_types::Text);
Some(v) => { diesel::select(sqlite_version).get_result::<String>(conn).unwrap_or_else(|_| "Unknown".to_string())
v.version
},
_ => "Unknown".to_string()
}
} }
} }
} }
@ -308,8 +288,7 @@ mod sqlite_migrations {
use diesel::{Connection, RunQueryDsl}; use diesel::{Connection, RunQueryDsl};
// Make sure the database is up to date (create if it doesn't exist, or run the migrations) // Make sure the database is up to date (create if it doesn't exist, or run the migrations)
let connection = let connection = diesel::sqlite::SqliteConnection::establish(&crate::CONFIG.database_url())?;
diesel::sqlite::SqliteConnection::establish(&crate::CONFIG.database_url())?;
// Disable Foreign Key Checks during migration // Disable Foreign Key Checks during migration
// Scoped to a connection. // Scoped to a connection.
@ -319,9 +298,7 @@ mod sqlite_migrations {
// Turn on WAL in SQLite // Turn on WAL in SQLite
if crate::CONFIG.enable_db_wal() { if crate::CONFIG.enable_db_wal() {
diesel::sql_query("PRAGMA journal_mode=wal") diesel::sql_query("PRAGMA journal_mode=wal").execute(&connection).expect("Failed to turn on WAL");
.execute(&connection)
.expect("Failed to turn on WAL");
} }
embedded_migrations::run_with_output(&connection, &mut std::io::stdout())?; embedded_migrations::run_with_output(&connection, &mut std::io::stdout())?;
@ -337,8 +314,7 @@ mod mysql_migrations {
pub fn run_migrations() -> Result<(), super::Error> { pub fn run_migrations() -> Result<(), super::Error> {
use diesel::{Connection, RunQueryDsl}; use diesel::{Connection, RunQueryDsl};
// Make sure the database is up to date (create if it doesn't exist, or run the migrations) // Make sure the database is up to date (create if it doesn't exist, or run the migrations)
let connection = let connection = diesel::mysql::MysqlConnection::establish(&crate::CONFIG.database_url())?;
diesel::mysql::MysqlConnection::establish(&crate::CONFIG.database_url())?;
// Disable Foreign Key Checks during migration // Disable Foreign Key Checks during migration
// Scoped to a connection/session. // Scoped to a connection/session.
@ -359,8 +335,7 @@ mod postgresql_migrations {
pub fn run_migrations() -> Result<(), super::Error> { pub fn run_migrations() -> Result<(), super::Error> {
use diesel::{Connection, RunQueryDsl}; use diesel::{Connection, RunQueryDsl};
// Make sure the database is up to date (create if it doesn't exist, or run the migrations) // Make sure the database is up to date (create if it doesn't exist, or run the migrations)
let connection = let connection = diesel::pg::PgConnection::establish(&crate::CONFIG.database_url())?;
diesel::pg::PgConnection::establish(&crate::CONFIG.database_url())?;
// Disable Foreign Key Checks during migration // Disable Foreign Key Checks during migration
// FIXME: Per https://www.postgresql.org/docs/12/sql-set-constraints.html, // FIXME: Per https://www.postgresql.org/docs/12/sql-set-constraints.html,

@ -59,7 +59,6 @@ use crate::error::MapResult;
/// Database methods /// Database methods
impl Attachment { impl Attachment {
pub fn save(&self, conn: &DbConn) -> EmptyResult { pub fn save(&self, conn: &DbConn) -> EmptyResult {
db_run! { conn: db_run! { conn:
sqlite, mysql { sqlite, mysql {

@ -1,15 +1,10 @@
use chrono::{NaiveDateTime, Utc}; use chrono::{Duration, NaiveDateTime, Utc};
use serde_json::Value; use serde_json::Value;
use crate::CONFIG;
use super::{ use super::{
Attachment, Attachment, CollectionCipher, Favorite, FolderCipher, Organization, User, UserOrgStatus, UserOrgType,
CollectionCipher,
Favorite,
FolderCipher,
Organization,
User,
UserOrgStatus,
UserOrgType,
UserOrganization, UserOrganization,
}; };
@ -91,16 +86,16 @@ impl Cipher {
}; };
let fields_json = self.fields.as_ref().and_then(|s| serde_json::from_str(s).ok()).unwrap_or(Value::Null); let fields_json = self.fields.as_ref().and_then(|s| serde_json::from_str(s).ok()).unwrap_or(Value::Null);
let password_history_json = self.password_history.as_ref().and_then(|s| serde_json::from_str(s).ok()).unwrap_or(Value::Null); let password_history_json =
self.password_history.as_ref().and_then(|s| serde_json::from_str(s).ok()).unwrap_or(Value::Null);
let (read_only, hide_passwords) = let (read_only, hide_passwords) = match self.get_access_restrictions(&user_uuid, conn) {
match self.get_access_restrictions(&user_uuid, conn) { Some((ro, hp)) => (ro, hp),
Some((ro, hp)) => (ro, hp), None => {
None => { error!("Cipher ownership assertion failure");
error!("Cipher ownership assertion failure"); (true, true)
(true, true) }
}, };
};
// Get the type_data or a default to an empty json object '{}'. // Get the type_data or a default to an empty json object '{}'.
// If not passing an empty object, mobile clients will crash. // If not passing an empty object, mobile clients will crash.
@ -130,7 +125,7 @@ impl Cipher {
// There are three types of cipher response models in upstream // There are three types of cipher response models in upstream
// Bitwarden: "cipherMini", "cipher", and "cipherDetails" (in order // Bitwarden: "cipherMini", "cipher", and "cipherDetails" (in order
// of increasing level of detail). bitwarden_rs currently only // of increasing level of detail). vaultwarden currently only
// supports the "cipherDetails" type, though it seems like the // supports the "cipherDetails" type, though it seems like the
// Bitwarden clients will ignore extra fields. // Bitwarden clients will ignore extra fields.
// //
@ -195,12 +190,10 @@ impl Cipher {
None => { None => {
// Belongs to Organization, need to update affected users // Belongs to Organization, need to update affected users
if let Some(ref org_uuid) = self.organization_uuid { if let Some(ref org_uuid) = self.organization_uuid {
UserOrganization::find_by_cipher_and_org(&self.uuid, &org_uuid, conn) UserOrganization::find_by_cipher_and_org(&self.uuid, &org_uuid, conn).iter().for_each(|user_org| {
.iter() User::update_uuid_revision(&user_org.user_uuid, conn);
.for_each(|user_org| { user_uuids.push(user_org.user_uuid.clone())
User::update_uuid_revision(&user_org.user_uuid, conn); });
user_uuids.push(user_org.user_uuid.clone())
});
} }
} }
}; };
@ -271,6 +264,17 @@ impl Cipher {
Ok(()) Ok(())
} }
/// Purge all ciphers that are old enough to be auto-deleted.
pub fn purge_trash(conn: &DbConn) {
if let Some(auto_delete_days) = CONFIG.trash_auto_delete_days() {
let now = Utc::now().naive_utc();
let dt = now - Duration::days(auto_delete_days);
for cipher in Self::find_deleted_before(&dt, conn) {
cipher.delete(&conn).ok();
}
}
}
pub fn move_to_folder(&self, folder_uuid: Option<String>, user_uuid: &str, conn: &DbConn) -> EmptyResult { pub fn move_to_folder(&self, folder_uuid: Option<String>, user_uuid: &str, conn: &DbConn) -> EmptyResult {
User::update_uuid_revision(user_uuid, conn); User::update_uuid_revision(user_uuid, conn);
@ -511,6 +515,15 @@ impl Cipher {
}} }}
} }
/// Find all ciphers that were deleted before the specified datetime.
pub fn find_deleted_before(dt: &NaiveDateTime, conn: &DbConn) -> Vec<Self> {
db_run! {conn: {
ciphers::table
.filter(ciphers::deleted_at.lt(dt))
.load::<CipherDb>(conn).expect("Error loading ciphers").from_db()
}}
}
pub fn get_collections(&self, user_id: &str, conn: &DbConn) -> Vec<String> { pub fn get_collections(&self, user_id: &str, conn: &DbConn) -> Vec<String> {
db_run! {conn: { db_run! {conn: {
ciphers_collections::table ciphers_collections::table

@ -1,6 +1,6 @@
use serde_json::Value; use serde_json::Value;
use super::{Organization, UserOrgStatus, UserOrgType, UserOrganization, User, Cipher}; use super::{Cipher, Organization, User, UserOrgStatus, UserOrgType, UserOrganization};
db_object! { db_object! {
#[derive(Identifiable, Queryable, Insertable, Associations, AsChangeset)] #[derive(Identifiable, Queryable, Insertable, Associations, AsChangeset)]
@ -127,11 +127,9 @@ impl Collection {
} }
pub fn update_users_revision(&self, conn: &DbConn) { pub fn update_users_revision(&self, conn: &DbConn) {
UserOrganization::find_by_collection_and_org(&self.uuid, &self.org_uuid, conn) UserOrganization::find_by_collection_and_org(&self.uuid, &self.org_uuid, conn).iter().for_each(|user_org| {
.iter() User::update_uuid_revision(&user_org.user_uuid, conn);
.for_each(|user_org| { });
User::update_uuid_revision(&user_org.user_uuid, conn);
});
} }
pub fn find_by_uuid(uuid: &str, conn: &DbConn) -> Option<Self> { pub fn find_by_uuid(uuid: &str, conn: &DbConn) -> Option<Self> {
@ -170,10 +168,7 @@ impl Collection {
} }
pub fn find_by_organization_and_user_uuid(org_uuid: &str, user_uuid: &str, conn: &DbConn) -> Vec<Self> { pub fn find_by_organization_and_user_uuid(org_uuid: &str, user_uuid: &str, conn: &DbConn) -> Vec<Self> {
Self::find_by_user_uuid(user_uuid, conn) Self::find_by_user_uuid(user_uuid, conn).into_iter().filter(|c| c.org_uuid == org_uuid).collect()
.into_iter()
.filter(|c| c.org_uuid == org_uuid)
.collect()
} }
pub fn find_by_organization(org_uuid: &str, conn: &DbConn) -> Vec<Self> { pub fn find_by_organization(org_uuid: &str, conn: &DbConn) -> Vec<Self> {
@ -284,7 +279,13 @@ impl CollectionUser {
}} }}
} }
pub fn save(user_uuid: &str, collection_uuid: &str, read_only: bool, hide_passwords: bool, conn: &DbConn) -> EmptyResult { pub fn save(
user_uuid: &str,
collection_uuid: &str,
read_only: bool,
hide_passwords: bool,
conn: &DbConn,
) -> EmptyResult {
User::update_uuid_revision(&user_uuid, conn); User::update_uuid_revision(&user_uuid, conn);
db_run! { conn: db_run! { conn:
@ -374,11 +375,9 @@ impl CollectionUser {
} }
pub fn delete_all_by_collection(collection_uuid: &str, conn: &DbConn) -> EmptyResult { pub fn delete_all_by_collection(collection_uuid: &str, conn: &DbConn) -> EmptyResult {
CollectionUser::find_by_collection(&collection_uuid, conn) CollectionUser::find_by_collection(&collection_uuid, conn).iter().for_each(|collection| {
.iter() User::update_uuid_revision(&collection.user_uuid, conn);
.for_each(|collection| { });
User::update_uuid_revision(&collection.user_uuid, conn);
});
db_run! { conn: { db_run! { conn: {
diesel::delete(users_collections::table.filter(users_collections::collection_uuid.eq(collection_uuid))) diesel::delete(users_collections::table.filter(users_collections::collection_uuid.eq(collection_uuid)))

@ -20,7 +20,7 @@ use crate::error::MapResult;
impl Favorite { impl Favorite {
// Returns whether the specified cipher is a favorite of the specified user. // Returns whether the specified cipher is a favorite of the specified user.
pub fn is_favorite(cipher_uuid: &str, user_uuid: &str, conn: &DbConn) -> bool { pub fn is_favorite(cipher_uuid: &str, user_uuid: &str, conn: &DbConn) -> bool {
db_run!{ conn: { db_run! { conn: {
let query = favorites::table let query = favorites::table
.filter(favorites::cipher_uuid.eq(cipher_uuid)) .filter(favorites::cipher_uuid.eq(cipher_uuid))
.filter(favorites::user_uuid.eq(user_uuid)) .filter(favorites::user_uuid.eq(user_uuid))
@ -36,19 +36,19 @@ impl Favorite {
match (old, new) { match (old, new) {
(false, true) => { (false, true) => {
User::update_uuid_revision(user_uuid, &conn); User::update_uuid_revision(user_uuid, &conn);
db_run!{ conn: { db_run! { conn: {
diesel::insert_into(favorites::table) diesel::insert_into(favorites::table)
.values(( .values((
favorites::user_uuid.eq(user_uuid), favorites::user_uuid.eq(user_uuid),
favorites::cipher_uuid.eq(cipher_uuid), favorites::cipher_uuid.eq(cipher_uuid),
)) ))
.execute(conn) .execute(conn)
.map_res("Error adding favorite") .map_res("Error adding favorite")
}} }}
} }
(true, false) => { (true, false) => {
User::update_uuid_revision(user_uuid, &conn); User::update_uuid_revision(user_uuid, &conn);
db_run!{ conn: { db_run! { conn: {
diesel::delete( diesel::delete(
favorites::table favorites::table
.filter(favorites::user_uuid.eq(user_uuid)) .filter(favorites::user_uuid.eq(user_uuid))
@ -59,7 +59,7 @@ impl Favorite {
}} }}
} }
// Otherwise, the favorite status is already what it should be. // Otherwise, the favorite status is already what it should be.
_ => Ok(()) _ => Ok(()),
} }
} }

@ -109,7 +109,6 @@ impl Folder {
User::update_uuid_revision(&self.user_uuid, conn); User::update_uuid_revision(&self.user_uuid, conn);
FolderCipher::delete_all_by_folder(&self.uuid, &conn)?; FolderCipher::delete_all_by_folder(&self.uuid, &conn)?;
db_run! { conn: { db_run! { conn: {
diesel::delete(folders::table.filter(folders::uuid.eq(&self.uuid))) diesel::delete(folders::table.filter(folders::uuid.eq(&self.uuid)))
.execute(conn) .execute(conn)

@ -6,9 +6,9 @@ mod favorite;
mod folder; mod folder;
mod org_policy; mod org_policy;
mod organization; mod organization;
mod send;
mod two_factor; mod two_factor;
mod user; mod user;
mod send;
pub use self::attachment::Attachment; pub use self::attachment::Attachment;
pub use self::cipher::Cipher; pub use self::cipher::Cipher;
@ -18,6 +18,6 @@ pub use self::favorite::Favorite;
pub use self::folder::{Folder, FolderCipher}; pub use self::folder::{Folder, FolderCipher};
pub use self::org_policy::{OrgPolicy, OrgPolicyType}; pub use self::org_policy::{OrgPolicy, OrgPolicyType};
pub use self::organization::{Organization, UserOrgStatus, UserOrgType, UserOrganization}; pub use self::organization::{Organization, UserOrgStatus, UserOrgType, UserOrganization};
pub use self::send::{Send, SendType};
pub use self::two_factor::{TwoFactor, TwoFactorType}; pub use self::two_factor::{TwoFactor, TwoFactorType};
pub use self::user::{Invitation, User, UserStampException}; pub use self::user::{Invitation, User, UserStampException};
pub use self::send::{Send, SendType};

@ -4,7 +4,7 @@ use crate::api::EmptyResult;
use crate::db::DbConn; use crate::db::DbConn;
use crate::error::MapResult; use crate::error::MapResult;
use super::{Organization, UserOrganization, UserOrgStatus, UserOrgType}; use super::{Organization, UserOrgStatus, UserOrgType, UserOrganization};
db_object! { db_object! {
#[derive(Identifiable, Queryable, Insertable, Associations, AsChangeset)] #[derive(Identifiable, Queryable, Insertable, Associations, AsChangeset)]
@ -20,8 +20,7 @@ db_object! {
} }
} }
#[derive(Copy, Clone)] #[derive(Copy, Clone, num_derive::FromPrimitive)]
#[derive(num_derive::FromPrimitive)]
pub enum OrgPolicyType { pub enum OrgPolicyType {
TwoFactorAuthentication = 0, TwoFactorAuthentication = 0,
MasterPassword = 1, MasterPassword = 1,
@ -175,7 +174,8 @@ impl OrgPolicy {
/// and the user is not an owner or admin of that org. This is only useful for checking /// and the user is not an owner or admin of that org. This is only useful for checking
/// applicability of policy types that have these particular semantics. /// applicability of policy types that have these particular semantics.
pub fn is_applicable_to_user(user_uuid: &str, policy_type: OrgPolicyType, conn: &DbConn) -> bool { pub fn is_applicable_to_user(user_uuid: &str, policy_type: OrgPolicyType, conn: &DbConn) -> bool {
for policy in OrgPolicy::find_by_user(user_uuid, conn) { // Returns confirmed users only. // Returns confirmed users only.
for policy in OrgPolicy::find_by_user(user_uuid, conn) {
if policy.enabled && policy.has_type(policy_type) { if policy.enabled && policy.has_type(policy_type) {
let org_uuid = &policy.org_uuid; let org_uuid = &policy.org_uuid;
if let Some(user) = UserOrganization::find_by_user_and_org(user_uuid, org_uuid, conn) { if let Some(user) = UserOrganization::find_by_user_and_org(user_uuid, org_uuid, conn) {

@ -1,8 +1,8 @@
use num_traits::FromPrimitive;
use serde_json::Value; use serde_json::Value;
use std::cmp::Ordering; use std::cmp::Ordering;
use num_traits::FromPrimitive;
use super::{CollectionUser, User, OrgPolicy}; use super::{CollectionUser, OrgPolicy, User};
db_object! { db_object! {
#[derive(Identifiable, Queryable, Insertable, AsChangeset)] #[derive(Identifiable, Queryable, Insertable, AsChangeset)]
@ -35,8 +35,7 @@ pub enum UserOrgStatus {
Confirmed = 2, Confirmed = 2,
} }
#[derive(Copy, Clone, PartialEq, Eq)] #[derive(Copy, Clone, PartialEq, Eq, num_derive::FromPrimitive)]
#[derive(num_derive::FromPrimitive)]
pub enum UserOrgType { pub enum UserOrgType {
Owner = 0, Owner = 0,
Admin = 1, Admin = 1,
@ -190,11 +189,9 @@ use crate::error::MapResult;
/// Database methods /// Database methods
impl Organization { impl Organization {
pub fn save(&self, conn: &DbConn) -> EmptyResult { pub fn save(&self, conn: &DbConn) -> EmptyResult {
UserOrganization::find_by_org(&self.uuid, conn) UserOrganization::find_by_org(&self.uuid, conn).iter().for_each(|user_org| {
.iter() User::update_uuid_revision(&user_org.user_uuid, conn);
.for_each(|user_org| { });
User::update_uuid_revision(&user_org.user_uuid, conn);
});
db_run! { conn: db_run! { conn:
sqlite, mysql { sqlite, mysql {
@ -236,7 +233,6 @@ impl Organization {
UserOrganization::delete_all_by_organization(&self.uuid, &conn)?; UserOrganization::delete_all_by_organization(&self.uuid, &conn)?;
OrgPolicy::delete_all_by_organization(&self.uuid, &conn)?; OrgPolicy::delete_all_by_organization(&self.uuid, &conn)?;
db_run! { conn: { db_run! { conn: {
diesel::delete(organizations::table.filter(organizations::uuid.eq(self.uuid))) diesel::delete(organizations::table.filter(organizations::uuid.eq(self.uuid)))
.execute(conn) .execute(conn)
@ -347,11 +343,13 @@ impl UserOrganization {
let collections = CollectionUser::find_by_organization_and_user_uuid(&self.org_uuid, &self.user_uuid, conn); let collections = CollectionUser::find_by_organization_and_user_uuid(&self.org_uuid, &self.user_uuid, conn);
collections collections
.iter() .iter()
.map(|c| json!({ .map(|c| {
"Id": c.collection_uuid, json!({
"ReadOnly": c.read_only, "Id": c.collection_uuid,
"HidePasswords": c.hide_passwords, "ReadOnly": c.read_only,
})) "HidePasswords": c.hide_passwords,
})
})
.collect() .collect()
}; };
@ -446,8 +444,7 @@ impl UserOrganization {
} }
pub fn has_full_access(&self) -> bool { pub fn has_full_access(&self) -> bool {
(self.access_all || self.atype >= UserOrgType::Admin) && (self.access_all || self.atype >= UserOrgType::Admin) && self.has_status(UserOrgStatus::Confirmed)
self.has_status(UserOrgStatus::Confirmed)
} }
pub fn find_by_uuid(uuid: &str, conn: &DbConn) -> Option<Self> { pub fn find_by_uuid(uuid: &str, conn: &DbConn) -> Option<Self> {

@ -205,6 +205,13 @@ impl Send {
}} }}
} }
/// Purge all sends that are past their deletion date.
pub fn purge(conn: &DbConn) {
for send in Self::find_by_past_deletion_date(&conn) {
send.delete(&conn).ok();
}
}
pub fn update_users_revision(&self, conn: &DbConn) { pub fn update_users_revision(&self, conn: &DbConn) {
match &self.user_uuid { match &self.user_uuid {
Some(user_uuid) => { Some(user_uuid) => {
@ -223,12 +230,6 @@ impl Send {
Ok(()) Ok(())
} }
pub fn find_all(conn: &DbConn) -> Vec<Self> {
db_run! {conn: {
sends::table.load::<SendDb>(conn).expect("Error loading sends").from_db()
}}
}
pub fn find_by_access_id(access_id: &str, conn: &DbConn) -> Option<Self> { pub fn find_by_access_id(access_id: &str, conn: &DbConn) -> Option<Self> {
use data_encoding::BASE64URL_NOPAD; use data_encoding::BASE64URL_NOPAD;
use uuid::Uuid; use uuid::Uuid;
@ -271,4 +272,13 @@ impl Send {
.load::<SendDb>(conn).expect("Error loading sends").from_db() .load::<SendDb>(conn).expect("Error loading sends").from_db()
}} }}
} }
pub fn find_by_past_deletion_date(conn: &DbConn) -> Vec<Self> {
let now = Utc::now().naive_utc();
db_run! {conn: {
sends::table
.filter(sends::deletion_date.lt(now))
.load::<SendDb>(conn).expect("Error loading sends").from_db()
}}
}
} }

@ -63,8 +63,8 @@ enum UserStatus {
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub struct UserStampException { pub struct UserStampException {
pub route: String, pub route: String,
pub security_stamp: String pub security_stamp: String,
} }
/// Local methods /// Local methods
@ -162,7 +162,7 @@ impl User {
pub fn set_stamp_exception(&mut self, route_exception: &str) { pub fn set_stamp_exception(&mut self, route_exception: &str) {
let stamp_exception = UserStampException { let stamp_exception = UserStampException {
route: route_exception.to_string(), route: route_exception.to_string(),
security_stamp: self.security_stamp.to_string() security_stamp: self.security_stamp.to_string(),
}; };
self.stamp_exception = Some(serde_json::to_string(&stamp_exception).unwrap_or_default()); self.stamp_exception = Some(serde_json::to_string(&stamp_exception).unwrap_or_default());
} }
@ -341,14 +341,16 @@ impl User {
pub fn last_active(&self, conn: &DbConn) -> Option<NaiveDateTime> { pub fn last_active(&self, conn: &DbConn) -> Option<NaiveDateTime> {
match Device::find_latest_active_by_user(&self.uuid, conn) { match Device::find_latest_active_by_user(&self.uuid, conn) {
Some(device) => Some(device.updated_at), Some(device) => Some(device.updated_at),
None => None None => None,
} }
} }
} }
impl Invitation { impl Invitation {
pub const fn new(email: String) -> Self { pub const fn new(email: String) -> Self {
Self { email } Self {
email,
}
} }
pub fn save(&self, conn: &DbConn) -> EmptyResult { pub fn save(&self, conn: &DbConn) -> EmptyResult {

@ -33,10 +33,10 @@ macro_rules! make_error {
}; };
} }
use diesel::r2d2::PoolError as R2d2Err;
use diesel::result::Error as DieselErr; use diesel::result::Error as DieselErr;
use diesel::ConnectionError as DieselConErr; use diesel::ConnectionError as DieselConErr;
use diesel_migrations::RunMigrationsError as DieselMigErr; use diesel_migrations::RunMigrationsError as DieselMigErr;
use diesel::r2d2::PoolError as R2d2Err;
use handlebars::RenderError as HbErr; use handlebars::RenderError as HbErr;
use jsonwebtoken::errors::Error as JwtErr; use jsonwebtoken::errors::Error as JwtErr;
use regex::Error as RegexErr; use regex::Error as RegexErr;
@ -191,18 +191,14 @@ use rocket::response::{self, Responder, Response};
impl<'r> Responder<'r> for Error { impl<'r> Responder<'r> for Error {
fn respond_to(self, _: &Request) -> response::Result<'r> { fn respond_to(self, _: &Request) -> response::Result<'r> {
match self.error { match self.error {
ErrorKind::EmptyError(_) => {} // Don't print the error in this situation ErrorKind::EmptyError(_) => {} // Don't print the error in this situation
ErrorKind::SimpleError(_) => {} // Don't print the error in this situation ErrorKind::SimpleError(_) => {} // Don't print the error in this situation
_ => error!(target: "error", "{:#?}", self), _ => error!(target: "error", "{:#?}", self),
}; };
let code = Status::from_code(self.error_code).unwrap_or(Status::BadRequest); let code = Status::from_code(self.error_code).unwrap_or(Status::BadRequest);
Response::build() Response::build().status(code).header(ContentType::JSON).sized_body(Cursor::new(format!("{}", self))).ok()
.status(code)
.header(ContentType::JSON)
.sized_body(Cursor::new(format!("{}", self)))
.ok()
} }
} }

@ -1,4 +1,4 @@
use std::{str::FromStr}; use std::str::FromStr;
use chrono::{DateTime, Local}; use chrono::{DateTime, Local};
use percent_encoding::{percent_encode, NON_ALPHANUMERIC}; use percent_encoding::{percent_encode, NON_ALPHANUMERIC};
@ -62,11 +62,13 @@ fn mailer() -> SmtpTransport {
let mut selected_mechanisms = vec![]; let mut selected_mechanisms = vec![];
for wanted_mechanism in mechanism.split(',') { for wanted_mechanism in mechanism.split(',') {
for m in &allowed_mechanisms { for m in &allowed_mechanisms {
if m.to_string().to_lowercase() == wanted_mechanism.trim_matches(|c| c == '"' || c == '\'' || c == ' ').to_lowercase() { if m.to_string().to_lowercase()
== wanted_mechanism.trim_matches(|c| c == '"' || c == '\'' || c == ' ').to_lowercase()
{
selected_mechanisms.push(*m); selected_mechanisms.push(*m);
} }
} }
}; }
if !selected_mechanisms.is_empty() { if !selected_mechanisms.is_empty() {
smtp_client.authentication(selected_mechanisms) smtp_client.authentication(selected_mechanisms)
@ -316,31 +318,23 @@ fn send_email(address: &str, subject: &str, body_html: String, body_text: String
let smtp_from = &CONFIG.smtp_from(); let smtp_from = &CONFIG.smtp_from();
let email = Message::builder() let email = Message::builder()
.message_id(Some(format!("<{}@{}>", crate::util::get_uuid(), smtp_from.split('@').collect::<Vec<&str>>()[1] ))) .message_id(Some(format!("<{}@{}>", crate::util::get_uuid(), smtp_from.split('@').collect::<Vec<&str>>()[1])))
.to(Mailbox::new(None, Address::from_str(&address)?)) .to(Mailbox::new(None, Address::from_str(&address)?))
.from(Mailbox::new( .from(Mailbox::new(Some(CONFIG.smtp_from_name()), Address::from_str(smtp_from)?))
Some(CONFIG.smtp_from_name()),
Address::from_str(smtp_from)?,
))
.subject(subject) .subject(subject)
.multipart( .multipart(MultiPart::alternative().singlepart(text).singlepart(html))?;
MultiPart::alternative()
.singlepart(text)
.singlepart(html)
)?;
match mailer().send(&email) { match mailer().send(&email) {
Ok(_) => Ok(()), Ok(_) => Ok(()),
// Match some common errors and make them more user friendly // Match some common errors and make them more user friendly
Err(e) => { Err(e) => {
if e.is_client() { if e.is_client() {
err!(format!("SMTP Client error: {}", e)); err!(format!("SMTP Client error: {}", e));
} else if e.is_transient() { } else if e.is_transient() {
err!(format!("SMTP 4xx error: {:?}", e)); err!(format!("SMTP 4xx error: {:?}", e));
} else if e.is_permanent() { } else if e.is_permanent() {
err!(format!("SMTP 5xx error: {:?}", e)); err!(format!("SMTP 5xx error: {:?}", e));
} else if e.is_timeout() { } else if e.is_timeout() {
err!(format!("SMTP timeout error: {:?}", e)); err!(format!("SMTP timeout error: {:?}", e));
} else { } else {
Err(e.into()) Err(e.into())

@ -16,6 +16,7 @@ extern crate diesel;
#[macro_use] #[macro_use]
extern crate diesel_migrations; extern crate diesel_migrations;
use job_scheduler::{Job, JobScheduler};
use std::{ use std::{
fs::create_dir_all, fs::create_dir_all,
panic, panic,
@ -23,6 +24,7 @@ use std::{
process::{exit, Command}, process::{exit, Command},
str::FromStr, str::FromStr,
thread, thread,
time::Duration,
}; };
#[macro_use] #[macro_use]
@ -56,14 +58,16 @@ fn main() {
create_icon_cache_folder(); create_icon_cache_folder();
launch_rocket(extra_debug); let pool = create_db_pool();
schedule_jobs(pool.clone());
launch_rocket(pool, extra_debug); // Blocks until program termination.
} }
const HELP: &str = "\ const HELP: &str = "\
A Bitwarden API server written in Rust Alternative implementation of the Bitwarden server API written in Rust
USAGE: USAGE:
bitwarden_rs vaultwarden
FLAGS: FLAGS:
-h, --help Prints help information -h, --help Prints help information
@ -75,18 +79,18 @@ fn parse_args() {
let mut pargs = pico_args::Arguments::from_env(); let mut pargs = pico_args::Arguments::from_env();
if pargs.contains(["-h", "--help"]) { if pargs.contains(["-h", "--help"]) {
println!("bitwarden_rs {}", option_env!("BWRS_VERSION").unwrap_or(NO_VERSION)); println!("vaultwarden {}", option_env!("BWRS_VERSION").unwrap_or(NO_VERSION));
print!("{}", HELP); print!("{}", HELP);
exit(0); exit(0);
} else if pargs.contains(["-v", "--version"]) { } else if pargs.contains(["-v", "--version"]) {
println!("bitwarden_rs {}", option_env!("BWRS_VERSION").unwrap_or(NO_VERSION)); println!("vaultwarden {}", option_env!("BWRS_VERSION").unwrap_or(NO_VERSION));
exit(0); exit(0);
} }
} }
fn launch_info() { fn launch_info() {
println!("/--------------------------------------------------------------------\\"); println!("/--------------------------------------------------------------------\\");
println!("| Starting Bitwarden_RS |"); println!("| Starting Vaultwarden |");
if let Some(version) = option_env!("BWRS_VERSION") { if let Some(version) = option_env!("BWRS_VERSION") {
println!("|{:^68}|", format!("Version {}", version)); println!("|{:^68}|", format!("Version {}", version));
@ -98,7 +102,7 @@ fn launch_info() {
println!("| Send usage/configuration questions or feature requests to: |"); println!("| Send usage/configuration questions or feature requests to: |");
println!("| https://bitwardenrs.discourse.group/ |"); println!("| https://bitwardenrs.discourse.group/ |");
println!("| Report suspected bugs/issues in the software itself at: |"); println!("| Report suspected bugs/issues in the software itself at: |");
println!("| https://github.com/dani-garcia/bitwarden_rs/issues/new |"); println!("| https://github.com/dani-garcia/vaultwarden/issues/new |");
println!("\\--------------------------------------------------------------------/\n"); println!("\\--------------------------------------------------------------------/\n");
} }
@ -123,7 +127,9 @@ fn init_logging(level: log::LevelFilter) -> Result<(), fern::InitError> {
// Enable smtp debug logging only specifically for smtp when need. // Enable smtp debug logging only specifically for smtp when need.
// This can contain sensitive information we do not want in the default debug/trace logging. // This can contain sensitive information we do not want in the default debug/trace logging.
if CONFIG.smtp_debug() { if CONFIG.smtp_debug() {
println!("[WARNING] SMTP Debugging is enabled (SMTP_DEBUG=true). Sensitive information could be disclosed via logs!"); println!(
"[WARNING] SMTP Debugging is enabled (SMTP_DEBUG=true). Sensitive information could be disclosed via logs!"
);
println!("[WARNING] Only enable SMTP_DEBUG during troubleshooting!\n"); println!("[WARNING] Only enable SMTP_DEBUG during troubleshooting!\n");
logger = logger.level_for("lettre::transport::smtp", log::LevelFilter::Debug) logger = logger.level_for("lettre::transport::smtp", log::LevelFilter::Debug)
} else { } else {
@ -201,7 +207,7 @@ fn chain_syslog(logger: fern::Dispatch) -> fern::Dispatch {
let syslog_fmt = syslog::Formatter3164 { let syslog_fmt = syslog::Formatter3164 {
facility: syslog::Facility::LOG_USER, facility: syslog::Facility::LOG_USER,
hostname: None, hostname: None,
process: "bitwarden_rs".into(), process: "vaultwarden".into(),
pid: 0, pid: 0,
}; };
@ -294,24 +300,27 @@ fn check_web_vault() {
let index_path = Path::new(&CONFIG.web_vault_folder()).join("index.html"); let index_path = Path::new(&CONFIG.web_vault_folder()).join("index.html");
if !index_path.exists() { if !index_path.exists() {
error!("Web vault is not found at '{}'. To install it, please follow the steps in: ", CONFIG.web_vault_folder()); error!(
error!("https://github.com/dani-garcia/bitwarden_rs/wiki/Building-binary#install-the-web-vault"); "Web vault is not found at '{}'. To install it, please follow the steps in: ",
CONFIG.web_vault_folder()
);
error!("https://github.com/dani-garcia/vaultwarden/wiki/Building-binary#install-the-web-vault");
error!("You can also set the environment variable 'WEB_VAULT_ENABLED=false' to disable it"); error!("You can also set the environment variable 'WEB_VAULT_ENABLED=false' to disable it");
exit(1); exit(1);
} }
} }
fn launch_rocket(extra_debug: bool) { fn create_db_pool() -> db::DbPool {
let pool = match util::retry_db(db::DbPool::from_config, CONFIG.db_connection_retries()) { match util::retry_db(db::DbPool::from_config, CONFIG.db_connection_retries()) {
Ok(p) => p, Ok(p) => p,
Err(e) => { Err(e) => {
error!("Error creating database pool: {:?}", e); error!("Error creating database pool: {:?}", e);
exit(1); exit(1);
} }
}; }
}
api::start_send_deletion_scheduler(pool.clone());
fn launch_rocket(pool: db::DbPool, extra_debug: bool) {
let basepath = &CONFIG.domain_path(); let basepath = &CONFIG.domain_path();
// If adding more paths here, consider also adding them to // If adding more paths here, consider also adding them to
@ -334,3 +343,40 @@ fn launch_rocket(extra_debug: bool) {
// The launch will restore the original logging level // The launch will restore the original logging level
error!("Launch error {:#?}", result); error!("Launch error {:#?}", result);
} }
fn schedule_jobs(pool: db::DbPool) {
if CONFIG.job_poll_interval_ms() == 0 {
info!("Job scheduler disabled.");
return;
}
thread::Builder::new()
.name("job-scheduler".to_string())
.spawn(move || {
let mut sched = JobScheduler::new();
// Purge sends that are past their deletion date.
if !CONFIG.send_purge_schedule().is_empty() {
sched.add(Job::new(CONFIG.send_purge_schedule().parse().unwrap(), || {
api::purge_sends(pool.clone());
}));
}
// Purge trashed items that are old enough to be auto-deleted.
if !CONFIG.trash_purge_schedule().is_empty() {
sched.add(Job::new(CONFIG.trash_purge_schedule().parse().unwrap(), || {
api::purge_trashed_ciphers(pool.clone());
}));
}
// Periodically check for jobs to run. We probably won't need any
// jobs that run more often than once a minute, so a default poll
// interval of 30 seconds should be sufficient. Users who want to
// schedule jobs to run more frequently for some reason can reduce
// the poll interval accordingly.
loop {
sched.tick();
thread::sleep(Duration::from_millis(CONFIG.job_poll_interval_ms()));
}
})
.expect("Error spawning job scheduler thread");
}

@ -772,7 +772,8 @@
"stackoverflow.com", "stackoverflow.com",
"serverfault.com", "serverfault.com",
"mathoverflow.net", "mathoverflow.net",
"askubuntu.com" "askubuntu.com",
"stackapps.com"
], ],
"Excluded": false "Excluded": false
}, },

Binary file not shown.

After

Width:  |  Height:  |  Size: 331 B

@ -5,7 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" /> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<meta name="robots" content="noindex,nofollow" /> <meta name="robots" content="noindex,nofollow" />
<link rel="icon" type="image/png" href="{{urlpath}}/bwrs_static/shield-white.png"> <link rel="icon" type="image/png" href="{{urlpath}}/bwrs_static/shield-white.png">
<title>Bitwarden_rs Admin Panel</title> <title>Vaultwarden Admin Panel</title>
<link rel="stylesheet" href="{{urlpath}}/bwrs_static/bootstrap.css" /> <link rel="stylesheet" href="{{urlpath}}/bwrs_static/bootstrap.css" />
<style> <style>
body { body {
@ -83,7 +83,7 @@
<body class="bg-light"> <body class="bg-light">
<nav class="navbar navbar-expand-md navbar-dark bg-dark mb-4 shadow fixed-top"> <nav class="navbar navbar-expand-md navbar-dark bg-dark mb-4 shadow fixed-top">
<div class="container-xl"> <div class="container-xl">
<a class="navbar-brand" href="{{urlpath}}/admin"><img class="pr-1" src="{{urlpath}}/bwrs_static/shield-white.png">Bitwarden_rs Admin</a> <a class="navbar-brand" href="{{urlpath}}/admin"><img class="pr-1" src="{{urlpath}}/bwrs_static/shield-white.png">Vaultwarden Admin</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarCollapse" <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarCollapse"
aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation"> aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span> <span class="navbar-toggler-icon"></span>

@ -20,6 +20,7 @@
<dd class="col-sm-7"> <dd class="col-sm-7">
<span id="server-latest">{{diagnostics.latest_release}}<span id="server-latest-commit" class="d-none">-{{diagnostics.latest_commit}}</span></span> <span id="server-latest">{{diagnostics.latest_release}}<span id="server-latest-commit" class="d-none">-{{diagnostics.latest_commit}}</span></span>
</dd> </dd>
{{#if diagnostics.web_vault_enabled}}
<dt class="col-sm-5">Web Installed <dt class="col-sm-5">Web Installed
<span class="badge badge-success d-none" id="web-success" title="Latest version is installed.">Ok</span> <span class="badge badge-success d-none" id="web-success" title="Latest version is installed.">Ok</span>
<span class="badge badge-warning d-none" id="web-warning" title="There seems to be an update available.">Update</span> <span class="badge badge-warning d-none" id="web-warning" title="There seems to be an update available.">Update</span>
@ -35,6 +36,13 @@
<span id="web-latest">{{diagnostics.latest_web_build}}</span> <span id="web-latest">{{diagnostics.latest_web_build}}</span>
</dd> </dd>
{{/unless}} {{/unless}}
{{/if}}
{{#unless diagnostics.web_vault_enabled}}
<dt class="col-sm-5">Web Installed</dt>
<dd class="col-sm-7">
<span id="web-installed">Web Vault is disabled</span>
</dd>
{{/unless}}
<dt class="col-sm-5">Database</dt> <dt class="col-sm-5">Database</dt>
<dd class="col-sm-7"> <dd class="col-sm-7">
<span><b>{{diagnostics.db_type}}:</b> {{diagnostics.db_version}}</span> <span><b>{{diagnostics.db_type}}:</b> {{diagnostics.db_version}}</span>
@ -118,7 +126,10 @@
<dd class="col-sm-7"> <dd class="col-sm-7">
<span id="dns-resolved">{{diagnostics.dns_resolved}}</span> <span id="dns-resolved">{{diagnostics.dns_resolved}}</span>
</dd> </dd>
<dt class="col-sm-5">Date & Time (Local)</dt>
<dd class="col-sm-7">
<span><b>Server:</b> {{diagnostics.server_time_local}}</span>
</dd>
<dt class="col-sm-5">Date & Time (UTC) <dt class="col-sm-5">Date & Time (UTC)
<span class="badge badge-success d-none" id="time-success" title="Time offsets seem to be correct.">Ok</span> <span class="badge badge-success d-none" id="time-success" title="Time offsets seem to be correct.">Ok</span>
<span class="badge badge-danger d-none" id="time-warning" title="Time offsets are too mouch at drift.">Error</span> <span class="badge badge-danger d-none" id="time-warning" title="Time offsets are too mouch at drift.">Error</span>
@ -148,8 +159,8 @@
<dl class="row"> <dl class="row">
<dd class="col-sm-12"> <dd class="col-sm-12">
If you need support please check the following links first before you create a new issue: If you need support please check the following links first before you create a new issue:
<a href="https://bitwardenrs.discourse.group/" target="_blank" rel="noreferrer">Bitwarden_RS Forum</a> <a href="https://bitwardenrs.discourse.group/" target="_blank" rel="noreferrer">Vaultwarden Forum</a>
| <a href="https://github.com/dani-garcia/bitwarden_rs/discussions" target="_blank" rel="noreferrer">Github Discussions</a> | <a href="https://github.com/dani-garcia/vaultwarden/discussions" target="_blank" rel="noreferrer">Github Discussions</a>
</dd> </dd>
</dl> </dl>
<dl class="row"> <dl class="row">
@ -211,7 +222,7 @@
} }
// ================================ // ================================
// Version check for both bitwarden_rs and web-vault // Version check for both vaultwarden and web-vault
let serverInstalled = document.getElementById('server-installed').innerText; let serverInstalled = document.getElementById('server-installed').innerText;
let serverLatest = document.getElementById('server-latest').innerText; let serverLatest = document.getElementById('server-latest').innerText;
let serverLatestCommit = document.getElementById('server-latest-commit').innerText.replace('-', ''); let serverLatestCommit = document.getElementById('server-latest-commit').innerText.replace('-', '');
@ -294,7 +305,7 @@
async function generateSupportString() { async function generateSupportString() {
supportString = "### Your environment (Generated via diagnostics page)\n"; supportString = "### Your environment (Generated via diagnostics page)\n";
supportString += "* Bitwarden_rs version: v{{ version }}\n"; supportString += "* Vaultwarden version: v{{ version }}\n";
supportString += "* Web-vault version: v{{ diagnostics.web_vault_version }}\n"; supportString += "* Web-vault version: v{{ diagnostics.web_vault_version }}\n";
supportString += "* Running within Docker: {{ diagnostics.running_within_docker }}\n"; supportString += "* Running within Docker: {{ diagnostics.running_within_docker }}\n";
supportString += "* Uses a reverse proxy: {{ diagnostics.ip_header_exists }}\n"; supportString += "* Uses a reverse proxy: {{ diagnostics.ip_header_exists }}\n";

@ -116,7 +116,11 @@
data-target="#g_database">Backup Database</button></div> data-target="#g_database">Backup Database</button></div>
<div id="g_database" class="card-body collapse" data-parent="#config-form"> <div id="g_database" class="card-body collapse" data-parent="#config-form">
<div class="small mb-3"> <div class="small mb-3">
NOTE: A local installation of sqlite3 is required for this section to work. WARNING: This function only creates a backup copy of the SQLite database.
This does not include any configuration or file attachment data that may
also be needed to fully restore a vaultwarden instance. For details on
how to perform complete backups, refer to the wiki page on
<a href="https://github.com/dani-garcia/vaultwarden/wiki/Backing-up-your-vault">backups</a>.
</div> </div>
<button type="button" class="btn btn-primary" onclick="backupDatabase();">Backup Database</button> <button type="button" class="btn btn-primary" onclick="backupDatabase();">Backup Database</button>
</div> </div>

@ -5,4 +5,4 @@ To finalize changing your email address enter the following code in web vault: {
If you did not try to change an email address, you can safely ignore this email. If you did not try to change an email address, you can safely ignore this email.
=== ===
Github: https://github.com/dani-garcia/bitwarden_rs Github: https://github.com/dani-garcia/vaultwarden

@ -4,7 +4,7 @@ Your Email Change
<head> <head>
<meta name="viewport" content="width=device-width" /> <meta name="viewport" content="width=device-width" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Bitwarden_rs</title> <title>Vaultwarden</title>
</head> </head>
<body style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; height: 100%; line-height: 25px; width: 100% !important;" bgcolor="#f6f6f6"> <body style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; height: 100%; line-height: 25px; width: 100% !important;" bgcolor="#f6f6f6">
<style type="text/css"> <style type="text/css">
@ -113,7 +113,7 @@ Your Email Change
<td class="aligncenter social-icons" align="center" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; padding: 15px 0 0 0;" valign="top"> <td class="aligncenter social-icons" align="center" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; padding: 15px 0 0 0;" valign="top">
<table cellpadding="0" cellspacing="0" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0 auto;"> <table cellpadding="0" cellspacing="0" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0 auto;">
<tr style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0;"> <tr style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0;">
<td style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; padding: 0 10px;" valign="top"><a href="https://github.com/dani-garcia/bitwarden_rs" target="_blank" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; text-decoration: underline;"><img src="{{url}}/bwrs_static/mail-github.png" alt="GitHub" width="30" height="30" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; border: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0; max-width: 100%;" /></a></td> <td style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; padding: 0 10px;" valign="top"><a href="https://github.com/dani-garcia/vaultwarden" target="_blank" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; text-decoration: underline;"><img src="{{url}}/bwrs_static/mail-github.png" alt="GitHub" width="30" height="30" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; border: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0; max-width: 100%;" /></a></td>
</tr> </tr>
</table> </table>
</td> </td>

@ -7,4 +7,4 @@ Delete Your Account: {{url}}/#/verify-recover-delete?userId={{user_id}}&token={{
If you did not request this email to delete your account, you can safely ignore this email. If you did not request this email to delete your account, you can safely ignore this email.
=== ===
Github: https://github.com/dani-garcia/bitwarden_rs Github: https://github.com/dani-garcia/vaultwarden

@ -4,7 +4,7 @@ Delete Your Account
<head> <head>
<meta name="viewport" content="width=device-width" /> <meta name="viewport" content="width=device-width" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Bitwarden_rs</title> <title>Vaultwarden</title>
</head> </head>
<body style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; height: 100%; line-height: 25px; width: 100% !important;" bgcolor="#f6f6f6"> <body style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; height: 100%; line-height: 25px; width: 100% !important;" bgcolor="#f6f6f6">
<style type="text/css"> <style type="text/css">
@ -121,7 +121,7 @@ Delete Your Account
<td class="aligncenter social-icons" align="center" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; padding: 15px 0 0 0;" valign="top"> <td class="aligncenter social-icons" align="center" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; padding: 15px 0 0 0;" valign="top">
<table cellpadding="0" cellspacing="0" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0 auto;"> <table cellpadding="0" cellspacing="0" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0 auto;">
<tr style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0;"> <tr style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0;">
<td style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; padding: 0 10px;" valign="top"><a href="https://github.com/dani-garcia/bitwarden_rs" target="_blank" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; text-decoration: underline;"><img src="{{url}}/bwrs_static/mail-github.png" alt="GitHub" width="30" height="30" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; border: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0; max-width: 100%;" /></a></td> <td style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; padding: 0 10px;" valign="top"><a href="https://github.com/dani-garcia/vaultwarden" target="_blank" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; text-decoration: underline;"><img src="{{url}}/bwrs_static/mail-github.png" alt="GitHub" width="30" height="30" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; border: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0; max-width: 100%;" /></a></td>
</tr> </tr>
</table> </table>
</td> </td>

@ -1,7 +1,7 @@
Invitation to {{{org_name}}} accepted Invitation to {{{org_name}}} accepted
<!----------------> <!---------------->
Your invitation for *{{email}}* to join *{{org_name}}* was accepted. Your invitation for *{{email}}* to join *{{org_name}}* was accepted.
Please log in via {{url}} to the bitwarden_rs server and confirm them from the organization management page. Please log in via {{url}} to the vaultwarden server and confirm them from the organization management page.
=== ===
Github: https://github.com/dani-garcia/bitwarden_rs Github: https://github.com/dani-garcia/vaultwarden

@ -4,7 +4,7 @@ Invitation to {{{org_name}}} accepted
<head> <head>
<meta name="viewport" content="width=device-width" /> <meta name="viewport" content="width=device-width" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Bitwarden_rs</title> <title>Vaultwarden</title>
</head> </head>
<body style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; height: 100%; line-height: 25px; width: 100% !important;" bgcolor="#f6f6f6"> <body style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; height: 100%; line-height: 25px; width: 100% !important;" bgcolor="#f6f6f6">
<style type="text/css"> <style type="text/css">
@ -101,7 +101,7 @@ Invitation to {{{org_name}}} accepted
</tr> </tr>
<tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;"> <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"> <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">
Please <a href="{{url}}/">log in</a> to the bitwarden_rs server and confirm them from the organization management page. Please <a href="{{url}}/">log in</a> to the vaultwarden server and confirm them from the organization management page.
</td> </td>
</tr> </tr>
<tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;"> <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;">
@ -118,7 +118,7 @@ Invitation to {{{org_name}}} accepted
<td class="aligncenter social-icons" align="center" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; padding: 15px 0 0 0;" valign="top"> <td class="aligncenter social-icons" align="center" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; padding: 15px 0 0 0;" valign="top">
<table cellpadding="0" cellspacing="0" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0 auto;"> <table cellpadding="0" cellspacing="0" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0 auto;">
<tr style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0;"> <tr style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0;">
<td style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; padding: 0 10px;" valign="top"><a href="https://github.com/dani-garcia/bitwarden_rs" target="_blank" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; text-decoration: underline;"><img src="{{url}}/bwrs_static/mail-github.png" alt="GitHub" width="30" height="30" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; border: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0; max-width: 100%;" /></a></td> <td style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; padding: 0 10px;" valign="top"><a href="https://github.com/dani-garcia/vaultwarden" target="_blank" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; text-decoration: underline;"><img src="{{url}}/bwrs_static/mail-github.png" alt="GitHub" width="30" height="30" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; border: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0; max-width: 100%;" /></a></td>
</tr> </tr>
</table> </table>
</td> </td>

@ -4,4 +4,4 @@ Your invitation to join *{{org_name}}* was confirmed.
It will now appear under the Organizations the next time you log in to the web vault at {{url}}. It will now appear under the Organizations the next time you log in to the web vault at {{url}}.
=== ===
Github: https://github.com/dani-garcia/bitwarden_rs Github: https://github.com/dani-garcia/vaultwarden

@ -4,7 +4,7 @@ Invitation to {{{org_name}}} confirmed
<head> <head>
<meta name="viewport" content="width=device-width" /> <meta name="viewport" content="width=device-width" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Bitwarden_rs</title> <title>Vaultwarden</title>
</head> </head>
<body style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; height: 100%; line-height: 25px; width: 100% !important;" bgcolor="#f6f6f6"> <body style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; height: 100%; line-height: 25px; width: 100% !important;" bgcolor="#f6f6f6">
<style type="text/css"> <style type="text/css">
@ -114,7 +114,7 @@ Invitation to {{{org_name}}} confirmed
<td class="aligncenter social-icons" align="center" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; padding: 15px 0 0 0;" valign="top"> <td class="aligncenter social-icons" align="center" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; padding: 15px 0 0 0;" valign="top">
<table cellpadding="0" cellspacing="0" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0 auto;"> <table cellpadding="0" cellspacing="0" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0 auto;">
<tr style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0;"> <tr style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0;">
<td style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; padding: 0 10px;" valign="top"><a href="https://github.com/dani-garcia/bitwarden_rs" target="_blank" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; text-decoration: underline;"><img src="{{url}}/bwrs_static/mail-github.png" alt="GitHub" width="30" height="30" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; border: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0; max-width: 100%;" /></a></td> <td style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; padding: 0 10px;" valign="top"><a href="https://github.com/dani-garcia/vaultwarden" target="_blank" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; text-decoration: underline;"><img src="{{url}}/bwrs_static/mail-github.png" alt="GitHub" width="30" height="30" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; border: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0; max-width: 100%;" /></a></td>
</tr> </tr>
</table> </table>
</td> </td>

@ -9,4 +9,4 @@ Your account was just logged into from a new device.
You can deauthorize all devices that have access to your account from the web vault ( {{url}} ) under Settings > My Account > Deauthorize Sessions. You can deauthorize all devices that have access to your account from the web vault ( {{url}} ) under Settings > My Account > Deauthorize Sessions.
=== ===
Github: https://github.com/dani-garcia/bitwarden_rs Github: https://github.com/dani-garcia/vaultwarden

@ -4,7 +4,7 @@ New Device Logged In From {{{device}}}
<head> <head>
<meta name="viewport" content="width=device-width" /> <meta name="viewport" content="width=device-width" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Bitwarden_rs</title> <title>Vaultwarden</title>
</head> </head>
<body style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; height: 100%; line-height: 25px; width: 100% !important;" bgcolor="#f6f6f6"> <body style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; height: 100%; line-height: 25px; width: 100% !important;" bgcolor="#f6f6f6">
<style type="text/css"> <style type="text/css">
@ -128,7 +128,7 @@ New Device Logged In From {{{device}}}
<td class="aligncenter social-icons" align="center" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; padding: 15px 0 0 0;" valign="top"> <td class="aligncenter social-icons" align="center" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; padding: 15px 0 0 0;" valign="top">
<table cellpadding="0" cellspacing="0" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0 auto;"> <table cellpadding="0" cellspacing="0" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0 auto;">
<tr style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0;"> <tr style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0;">
<td style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; padding: 0 10px;" valign="top"><a href="https://github.com/dani-garcia/bitwarden_rs" target="_blank" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; text-decoration: underline;"><img src="{{url}}/bwrs_static/mail-github.png" alt="GitHub" width="30" height="30" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; border: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0; max-width: 100%;" /></a></td> <td style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; padding: 0 10px;" valign="top"><a href="https://github.com/dani-garcia/vaultwarden" target="_blank" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; text-decoration: underline;"><img src="{{url}}/bwrs_static/mail-github.png" alt="GitHub" width="30" height="30" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; border: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0; max-width: 100%;" /></a></td>
</tr> </tr>
</table> </table>
</td> </td>

@ -7,4 +7,4 @@ If you cannot remember your master password, there is no way to recover your dat
If you did not request your master password hint you can safely ignore this email. If you did not request your master password hint you can safely ignore this email.
=== ===
Github: https://github.com/dani-garcia/bitwarden_rs Github: https://github.com/dani-garcia/vaultwarden

@ -4,7 +4,7 @@ Sorry, you have no password hint...
<head> <head>
<meta name="viewport" content="width=device-width" /> <meta name="viewport" content="width=device-width" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Bitwarden_rs</title> <title>Vaultwarden</title>
</head> </head>
<body style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; height: 100%; line-height: 25px; width: 100% !important;" bgcolor="#f6f6f6"> <body style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; height: 100%; line-height: 25px; width: 100% !important;" bgcolor="#f6f6f6">
<style type="text/css"> <style type="text/css">
@ -118,7 +118,7 @@ Sorry, you have no password hint...
<td class="aligncenter social-icons" align="center" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; padding: 15px 0 0 0;" valign="top"> <td class="aligncenter social-icons" align="center" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; padding: 15px 0 0 0;" valign="top">
<table cellpadding="0" cellspacing="0" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0 auto;"> <table cellpadding="0" cellspacing="0" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0 auto;">
<tr style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0;"> <tr style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0;">
<td style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; padding: 0 10px;" valign="top"><a href="https://github.com/dani-garcia/bitwarden_rs" target="_blank" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; text-decoration: underline;"><img src="{{url}}/bwrs_static/mail-github.png" alt="GitHub" width="30" height="30" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; border: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0; max-width: 100%;" /></a></td> <td style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; padding: 0 10px;" valign="top"><a href="https://github.com/dani-garcia/vaultwarden" target="_blank" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; text-decoration: underline;"><img src="{{url}}/bwrs_static/mail-github.png" alt="GitHub" width="30" height="30" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; border: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0; max-width: 100%;" /></a></td>
</tr> </tr>
</table> </table>
</td> </td>

@ -10,4 +10,4 @@ If you cannot remember your master password, there is no way to recover your dat
If you did not request your master password hint you can safely ignore this email. If you did not request your master password hint you can safely ignore this email.
=== ===
Github: https://github.com/dani-garcia/bitwarden_rs Github: https://github.com/dani-garcia/vaultwarden

@ -4,7 +4,7 @@ Your master password hint
<head> <head>
<meta name="viewport" content="width=device-width" /> <meta name="viewport" content="width=device-width" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Bitwarden_rs</title> <title>Vaultwarden</title>
</head> </head>
<body style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; height: 100%; line-height: 25px; width: 100% !important;" bgcolor="#f6f6f6"> <body style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; height: 100%; line-height: 25px; width: 100% !important;" bgcolor="#f6f6f6">
<style type="text/css"> <style type="text/css">
@ -124,7 +124,7 @@ Your master password hint
<td class="aligncenter social-icons" align="center" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; padding: 15px 0 0 0;" valign="top"> <td class="aligncenter social-icons" align="center" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; padding: 15px 0 0 0;" valign="top">
<table cellpadding="0" cellspacing="0" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0 auto;"> <table cellpadding="0" cellspacing="0" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0 auto;">
<tr style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0;"> <tr style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0;">
<td style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; padding: 0 10px;" valign="top"><a href="https://github.com/dani-garcia/bitwarden_rs" target="_blank" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; text-decoration: underline;"><img src="{{url}}/bwrs_static/mail-github.png" alt="GitHub" width="30" height="30" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; border: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0; max-width: 100%;" /></a></td> <td style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; padding: 0 10px;" valign="top"><a href="https://github.com/dani-garcia/vaultwarden" target="_blank" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; text-decoration: underline;"><img src="{{url}}/bwrs_static/mail-github.png" alt="GitHub" width="30" height="30" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; border: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0; max-width: 100%;" /></a></td>
</tr> </tr>
</table> </table>
</td> </td>

@ -9,4 +9,4 @@ Click here to join: {{url}}/#/accept-organization/?organizationId={{org_id}}&org
If you do not wish to join this organization, you can safely ignore this email. If you do not wish to join this organization, you can safely ignore this email.
=== ===
Github: https://github.com/dani-garcia/bitwarden_rs Github: https://github.com/dani-garcia/vaultwarden

@ -4,7 +4,7 @@ Join {{{org_name}}}
<head> <head>
<meta name="viewport" content="width=device-width" /> <meta name="viewport" content="width=device-width" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Bitwarden_rs</title> <title>Vaultwarden</title>
</head> </head>
<body style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; height: 100%; line-height: 25px; width: 100% !important;" bgcolor="#f6f6f6"> <body style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; height: 100%; line-height: 25px; width: 100% !important;" bgcolor="#f6f6f6">
<style type="text/css"> <style type="text/css">
@ -121,7 +121,7 @@ Join {{{org_name}}}
<td class="aligncenter social-icons" align="center" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; padding: 15px 0 0 0;" valign="top"> <td class="aligncenter social-icons" align="center" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; padding: 15px 0 0 0;" valign="top">
<table cellpadding="0" cellspacing="0" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0 auto;"> <table cellpadding="0" cellspacing="0" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0 auto;">
<tr style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0;"> <tr style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0;">
<td style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; padding: 0 10px;" valign="top"><a href="https://github.com/dani-garcia/bitwarden_rs" target="_blank" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; text-decoration: underline;"><img src="{{url}}/bwrs_static/mail-github.png" alt="GitHub" width="30" height="30" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; border: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0; max-width: 100%;" /></a></td> <td style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; padding: 0 10px;" valign="top"><a href="https://github.com/dani-garcia/vaultwarden" target="_blank" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; text-decoration: underline;"><img src="{{url}}/bwrs_static/mail-github.png" alt="GitHub" width="30" height="30" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; border: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0; max-width: 100%;" /></a></td>
</tr> </tr>
</table> </table>
</td> </td>

@ -1,8 +1,8 @@
Bitwarden_rs SMTP Test Vaultwarden SMTP Test
<!----------------> <!---------------->
This is a test email to verify the SMTP configuration for {{url}}. This is a test email to verify the SMTP configuration for {{url}}.
When you can read this email it is probably configured correctly. When you can read this email it is probably configured correctly.
=== ===
Github: https://github.com/dani-garcia/bitwarden_rs Github: https://github.com/dani-garcia/vaultwarden

@ -1,10 +1,10 @@
Bitwarden_rs SMTP Test Vaultwarden SMTP Test
<!----------------> <!---------------->
<html xmlns="http://www.w3.org/1999/xhtml" xmlns="http://www.w3.org/1999/xhtml" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0;"> <html xmlns="http://www.w3.org/1999/xhtml" xmlns="http://www.w3.org/1999/xhtml" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0;">
<head> <head>
<meta name="viewport" content="width=device-width" /> <meta name="viewport" content="width=device-width" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Bitwarden_rs</title> <title>Vaultwarden</title>
</head> </head>
<body style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; height: 100%; line-height: 25px; width: 100% !important;" bgcolor="#f6f6f6"> <body style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; height: 100%; line-height: 25px; width: 100% !important;" bgcolor="#f6f6f6">
<style type="text/css"> <style type="text/css">
@ -113,7 +113,7 @@ Bitwarden_rs SMTP Test
<td class="aligncenter social-icons" align="center" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; padding: 15px 0 0 0;" valign="top"> <td class="aligncenter social-icons" align="center" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; padding: 15px 0 0 0;" valign="top">
<table cellpadding="0" cellspacing="0" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0 auto;"> <table cellpadding="0" cellspacing="0" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0 auto;">
<tr style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0;"> <tr style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0;">
<td style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; padding: 0 10px;" valign="top"><a href="https://github.com/dani-garcia/bitwarden_rs" target="_blank" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; text-decoration: underline;"><img src="{{url}}/bwrs_static/mail-github.png" alt="GitHub" width="30" height="30" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; border: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0; max-width: 100%;" /></a></td> <td style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; padding: 0 10px;" valign="top"><a href="https://github.com/dani-garcia/vaultwarden" target="_blank" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; text-decoration: underline;"><img src="{{url}}/bwrs_static/mail-github.png" alt="GitHub" width="30" height="30" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; border: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0; max-width: 100%;" /></a></td>
</tr> </tr>
</table> </table>
</td> </td>

@ -5,4 +5,4 @@ Your two-step verification code is: {{token}}
Use this code to complete logging in with Bitwarden. Use this code to complete logging in with Bitwarden.
=== ===
Github: https://github.com/dani-garcia/bitwarden_rs Github: https://github.com/dani-garcia/vaultwarden

@ -4,7 +4,7 @@ Your Two-step Login Verification Code
<head> <head>
<meta name="viewport" content="width=device-width" /> <meta name="viewport" content="width=device-width" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Bitwarden_rs</title> <title>Vaultwarden</title>
</head> </head>
<body style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; height: 100%; line-height: 25px; width: 100% !important;" bgcolor="#f6f6f6"> <body style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; height: 100%; line-height: 25px; width: 100% !important;" bgcolor="#f6f6f6">
<style type="text/css"> <style type="text/css">
@ -113,7 +113,7 @@ Your Two-step Login Verification Code
<td class="aligncenter social-icons" align="center" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; padding: 15px 0 0 0;" valign="top"> <td class="aligncenter social-icons" align="center" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; padding: 15px 0 0 0;" valign="top">
<table cellpadding="0" cellspacing="0" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0 auto;"> <table cellpadding="0" cellspacing="0" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0 auto;">
<tr style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0;"> <tr style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0;">
<td style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; padding: 0 10px;" valign="top"><a href="https://github.com/dani-garcia/bitwarden_rs" target="_blank" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; text-decoration: underline;"><img src="{{url}}/bwrs_static/mail-github.png" alt="GitHub" width="30" height="30" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; border: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0; max-width: 100%;" /></a></td> <td style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; padding: 0 10px;" valign="top"><a href="https://github.com/dani-garcia/vaultwarden" target="_blank" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; text-decoration: underline;"><img src="{{url}}/bwrs_static/mail-github.png" alt="GitHub" width="30" height="30" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; border: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0; max-width: 100%;" /></a></td>
</tr> </tr>
</table> </table>
</td> </td>

@ -7,4 +7,4 @@ Verify Email Address Now: {{url}}/#/verify-email/?userId={{user_id}}&token={{tok
If you did not request to verify your account, you can safely ignore this email. If you did not request to verify your account, you can safely ignore this email.
=== ===
Github: https://github.com/dani-garcia/bitwarden_rs Github: https://github.com/dani-garcia/vaultwarden

@ -4,7 +4,7 @@ Verify Your Email
<head> <head>
<meta name="viewport" content="width=device-width" /> <meta name="viewport" content="width=device-width" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Bitwarden_rs</title> <title>Vaultwarden</title>
</head> </head>
<body style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; height: 100%; line-height: 25px; width: 100% !important;" bgcolor="#f6f6f6"> <body style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; height: 100%; line-height: 25px; width: 100% !important;" bgcolor="#f6f6f6">
<style type="text/css"> <style type="text/css">
@ -121,7 +121,7 @@ Verify Your Email
<td class="aligncenter social-icons" align="center" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; padding: 15px 0 0 0;" valign="top"> <td class="aligncenter social-icons" align="center" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; padding: 15px 0 0 0;" valign="top">
<table cellpadding="0" cellspacing="0" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0 auto;"> <table cellpadding="0" cellspacing="0" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0 auto;">
<tr style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0;"> <tr style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0;">
<td style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; padding: 0 10px;" valign="top"><a href="https://github.com/dani-garcia/bitwarden_rs" target="_blank" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; text-decoration: underline;"><img src="{{url}}/bwrs_static/mail-github.png" alt="GitHub" width="30" height="30" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; border: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0; max-width: 100%;" /></a></td> <td style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; padding: 0 10px;" valign="top"><a href="https://github.com/dani-garcia/vaultwarden" target="_blank" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; text-decoration: underline;"><img src="{{url}}/bwrs_static/mail-github.png" alt="GitHub" width="30" height="30" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; border: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0; max-width: 100%;" /></a></td>
</tr> </tr>
</table> </table>
</td> </td>

@ -5,4 +5,4 @@ Thank you for creating an account at {{url}}. You may now log in with your new a
If you did not request to create an account, you can safely ignore this email. If you did not request to create an account, you can safely ignore this email.
=== ===
Github: https://github.com/dani-garcia/bitwarden_rs Github: https://github.com/dani-garcia/vaultwarden

@ -4,7 +4,7 @@ Welcome
<head> <head>
<meta name="viewport" content="width=device-width" /> <meta name="viewport" content="width=device-width" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Bitwarden_rs</title> <title>Vaultwarden</title>
</head> </head>
<body style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; height: 100%; line-height: 25px; width: 100% !important;" bgcolor="#f6f6f6"> <body style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; height: 100%; line-height: 25px; width: 100% !important;" bgcolor="#f6f6f6">
<style type="text/css"> <style type="text/css">
@ -113,7 +113,7 @@ Welcome
<td class="aligncenter social-icons" align="center" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; padding: 15px 0 0 0;" valign="top"> <td class="aligncenter social-icons" align="center" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; padding: 15px 0 0 0;" valign="top">
<table cellpadding="0" cellspacing="0" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0 auto;"> <table cellpadding="0" cellspacing="0" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0 auto;">
<tr style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0;"> <tr style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0;">
<td style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; padding: 0 10px;" valign="top"><a href="https://github.com/dani-garcia/bitwarden_rs" target="_blank" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; text-decoration: underline;"><img src="{{url}}/bwrs_static/mail-github.png" alt="GitHub" width="30" height="30" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; border: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0; max-width: 100%;" /></a></td> <td style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; padding: 0 10px;" valign="top"><a href="https://github.com/dani-garcia/vaultwarden" target="_blank" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; text-decoration: underline;"><img src="{{url}}/bwrs_static/mail-github.png" alt="GitHub" width="30" height="30" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; border: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0; max-width: 100%;" /></a></td>
</tr> </tr>
</table> </table>
</td> </td>

@ -7,4 +7,4 @@ Verify Email Address Now: {{url}}/#/verify-email/?userId={{user_id}}&token={{tok
If you did not request to create an account, you can safely ignore this email. If you did not request to create an account, you can safely ignore this email.
=== ===
Github: https://github.com/dani-garcia/bitwarden_rs Github: https://github.com/dani-garcia/vaultwarden

@ -4,7 +4,7 @@ Welcome
<head> <head>
<meta name="viewport" content="width=device-width" /> <meta name="viewport" content="width=device-width" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Bitwarden_rs</title> <title>Vaultwarden</title>
</head> </head>
<body style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; height: 100%; line-height: 25px; width: 100% !important;" bgcolor="#f6f6f6"> <body style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; height: 100%; line-height: 25px; width: 100% !important;" bgcolor="#f6f6f6">
<style type="text/css"> <style type="text/css">
@ -121,7 +121,7 @@ Welcome
<td class="aligncenter social-icons" align="center" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; padding: 15px 0 0 0;" valign="top"> <td class="aligncenter social-icons" align="center" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; padding: 15px 0 0 0;" valign="top">
<table cellpadding="0" cellspacing="0" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0 auto;"> <table cellpadding="0" cellspacing="0" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0 auto;">
<tr style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0;"> <tr style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0;">
<td style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; padding: 0 10px;" valign="top"><a href="https://github.com/dani-garcia/bitwarden_rs" target="_blank" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; text-decoration: underline;"><img src="{{url}}/bwrs_static/mail-github.png" alt="GitHub" width="30" height="30" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; border: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0; max-width: 100%;" /></a></td> <td style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; padding: 0 10px;" valign="top"><a href="https://github.com/dani-garcia/vaultwarden" target="_blank" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; text-decoration: underline;"><img src="{{url}}/bwrs_static/mail-github.png" alt="GitHub" width="30" height="30" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; border: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0; max-width: 100%;" /></a></td>
</tr> </tr>
</table> </table>
</td> </td>

@ -28,7 +28,10 @@ impl Fairing for AppHeaders {
res.set_raw_header("X-Frame-Options", "SAMEORIGIN"); res.set_raw_header("X-Frame-Options", "SAMEORIGIN");
res.set_raw_header("X-Content-Type-Options", "nosniff"); res.set_raw_header("X-Content-Type-Options", "nosniff");
res.set_raw_header("X-XSS-Protection", "1; mode=block"); res.set_raw_header("X-XSS-Protection", "1; mode=block");
let csp = format!("frame-ancestors 'self' chrome-extension://nngceckbapebfimnlniiiahkandclblb moz-extension://* {};", CONFIG.allowed_iframe_ancestors()); let csp = format!(
"frame-ancestors 'self' chrome-extension://nngceckbapebfimnlniiiahkandclblb moz-extension://* {};",
CONFIG.allowed_iframe_ancestors()
);
res.set_raw_header("Content-Security-Policy", csp); res.set_raw_header("Content-Security-Policy", csp);
// Disable cache unless otherwise specified // Disable cache unless otherwise specified
@ -124,14 +127,8 @@ impl<'r, R: Responder<'r>> Responder<'r> for Cached<R> {
// Log all the routes from the main paths list, and the attachments endpoint // Log all the routes from the main paths list, and the attachments endpoint
// Effectively ignores, any static file route, and the alive endpoint // Effectively ignores, any static file route, and the alive endpoint
const LOGGED_ROUTES: [&str; 6] = [ const LOGGED_ROUTES: [&str; 6] =
"/api", ["/api", "/admin", "/identity", "/icons", "/notifications/hub/negotiate", "/attachments"];
"/admin",
"/identity",
"/icons",
"/notifications/hub/negotiate",
"/attachments",
];
// Boolean is extra debug, when true, we ignore the whitelist above and also print the mounts // Boolean is extra debug, when true, we ignore the whitelist above and also print the mounts
pub struct BetterLogging(pub bool); pub struct BetterLogging(pub bool);
@ -158,7 +155,11 @@ impl Fairing for BetterLogging {
} }
let config = rocket.config(); let config = rocket.config();
let scheme = if config.tls_enabled() { "https" } else { "http" }; let scheme = if config.tls_enabled() {
"https"
} else {
"http"
};
let addr = format!("{}://{}:{}", &scheme, &config.address, &config.port); let addr = format!("{}://{}:{}", &scheme, &config.address, &config.port);
info!(target: "start", "Rocket has launched from {}", addr); info!(target: "start", "Rocket has launched from {}", addr);
} }
@ -293,8 +294,7 @@ where
use std::env; use std::env;
pub fn get_env_str_value(key: &str) -> Option<String> pub fn get_env_str_value(key: &str) -> Option<String> {
{
let key_file = format!("{}_FILE", key); let key_file = format!("{}_FILE", key);
let value_from_env = env::var(key); let value_from_env = env::var(key);
let value_file = env::var(&key_file); let value_file = env::var(&key_file);
@ -304,9 +304,9 @@ pub fn get_env_str_value(key: &str) -> Option<String>
(Ok(v_env), Err(_)) => Some(v_env), (Ok(v_env), Err(_)) => Some(v_env),
(Err(_), Ok(v_file)) => match fs::read_to_string(v_file) { (Err(_), Ok(v_file)) => match fs::read_to_string(v_file) {
Ok(content) => Some(content.trim().to_string()), Ok(content) => Some(content.trim().to_string()),
Err(e) => panic!("Failed to load {}: {:?}", key, e) Err(e) => panic!("Failed to load {}: {:?}", key, e),
}, },
_ => None _ => None,
} }
} }
@ -478,7 +478,6 @@ pub fn retry<F, T, E>(func: F, max_tries: u32) -> Result<T, E>
where where
F: Fn() -> Result<T, E>, F: Fn() -> Result<T, E>,
{ {
use std::{thread::sleep, time::Duration};
let mut tries = 0; let mut tries = 0;
loop { loop {
@ -497,12 +496,13 @@ where
} }
} }
use std::{thread::sleep, time::Duration};
pub fn retry_db<F, T, E>(func: F, max_tries: u32) -> Result<T, E> pub fn retry_db<F, T, E>(func: F, max_tries: u32) -> Result<T, E>
where where
F: Fn() -> Result<T, E>, F: Fn() -> Result<T, E>,
E: std::error::Error, E: std::error::Error,
{ {
use std::{thread::sleep, time::Duration};
let mut tries = 0; let mut tries = 0;
loop { loop {
@ -522,3 +522,18 @@ where
} }
} }
} }
use reqwest::{
blocking::{Client, ClientBuilder},
header,
};
pub fn get_reqwest_client() -> Client {
get_reqwest_client_builder().build().expect("Failed to build client")
}
pub fn get_reqwest_client_builder() -> ClientBuilder {
let mut headers = header::HeaderMap::new();
headers.insert(header::USER_AGENT, header::HeaderValue::from_static("Vaultwarden"));
Client::builder().default_headers(headers).timeout(Duration::from_secs(10))
}

Loading…
Cancel
Save