Compare commits

...

78 Commits

Author SHA1 Message Date
Daniel García
a0eab35768 Update web vault to 2022.6.2 2022-07-15 19:15:22 +02:00
Daniel García
027c87dd07 Merge branch 'BlackDex-update-dep-fix-issue-2516' into main 2022-07-15 19:14:21 +02:00
Daniel García
f2b31352fe Merge branch 'update-dep-fix-issue-2516' of https://github.com/BlackDex/vaultwarden into BlackDex-update-dep-fix-issue-2516 2022-07-15 19:14:14 +02:00
Daniel García
c9376e3126 Remove read_file and read_file_string and replace them with the std alternatives 2022-07-15 19:13:26 +02:00
Daniel García
7cbcad0e38 Merge branch 'BlackDex-more-clippy-checks' into main 2022-07-15 19:06:09 +02:00
Daniel García
e167798449 Merge branch 'more-clippy-checks' of https://github.com/BlackDex/vaultwarden into BlackDex-more-clippy-checks 2022-07-15 19:05:54 +02:00
Daniel García
fc5928772b Move around comments 2022-07-15 19:05:38 +02:00
Daniel García
8263bdd21d Merge branch 'ruifung-main' into main 2022-07-15 19:03:49 +02:00
BlackDex
3c1d4254e7 Update deps and fix file-uploads
- Update deps. One of them is multer-rs which fixes #2516
- Changed MSRV to `1.59.0`, since that is the correct MSRV currently.
  It could be lower, but that would mean removing the `strip` option.
2022-07-15 16:03:57 +02:00
BlackDex
55d7c48b1d Add more clippy checks for better code/readability
A bit inspired by @paolobarbolini from this commit at lettre https://github.com/lettre/lettre/pull/784 .
I added a few more clippy lints here, and fixed the resulted issues.

Overall i think this could help in preventing future issues, and maybe
even peformance problems. It also makes some code a bit more clear.

We could always add more if we want to, i left a few out which i think
arn't that huge of an issue. Some like the `unused_async` are nice,
which resulted in a few `async` removals.

Some others are maybe a bit more estatic, like `string_to_string`, but i
think it looks better to use `clone` in those cases instead of `to_string` while they already are a string.
2022-07-10 16:39:38 +02:00
Yip Rui Fung
bf623eed7f Use if let instead of a match with empty block. 2022-07-09 11:43:00 +08:00
Yip Rui Fung
84bcac0112 Apply rustfmt.
Because apparently CLion's default formatting is not the same as rustfmt for some reason.
2022-07-09 10:49:51 +08:00
Yip Rui Fung
31595888ea Use match to avoid ownership issues on the TempFile / file_path variables in closures. 2022-07-09 10:33:27 +08:00
Yip Rui Fung
5c38b2c4eb Remove option and use unwrap_or_else to fall back to copy behavior. 2022-07-09 08:53:00 +08:00
Yip Rui Fung
ebe9162af9 Add option to make file uploads use move_copy_to instead of persist_to
This is to support scenarios where the attachments and sends folder are to be stored on a separate device from the tmp_folder (i.e. fuse-mounted S3 storage), due to having the tmp_dir on the same device being undesirable.

Example being fuse-mounted S3 storage with the reasoning that because S3 basically requires a copy+delete operations to rename files, it's inefficient to rename files on device, if it's even allowed.
2022-07-09 01:19:00 +08:00
Daniel García
b64cf27038 Upgrade dependencies and swap lettre to async transport 2022-07-06 23:57:37 +02:00
Daniel García
0c4e79cff6 Update web vault to v2022.6.0 2022-07-06 23:35:02 +02:00
Daniel García
5b9129a086 Merge remote-tracking branch 'origin/dependabot/cargo/openssl-src-111.22.01.1.1q' into main 2022-07-06 23:30:49 +02:00
Daniel García
93d4a12834 Update the rest of the files leftover from #2595 by running make 2022-07-06 23:27:48 +02:00
Daniel García
bf3e2dc652 Merge branch 'nneul-patch-1' into main 2022-07-06 23:26:54 +02:00
dependabot[bot]
0d0e98d783 Bump openssl-src from 111.21.0+1.1.1p to 111.22.0+1.1.1q
Bumps [openssl-src](https://github.com/alexcrichton/openssl-src-rs) from 111.21.0+1.1.1p to 111.22.0+1.1.1q.
- [Release notes](https://github.com/alexcrichton/openssl-src-rs/releases)
- [Commits](https://github.com/alexcrichton/openssl-src-rs/commits)

---
updated-dependencies:
- dependency-name: openssl-src
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-07-06 20:16:56 +00:00
Nathan Neulinger
5a55cfbb9b Update Dockerfile.j2 2022-07-06 08:56:17 -05:00
Nathan Neulinger
ac93b8a6b9 Update Dockerfile.buildx.alpine 2022-07-06 08:54:36 -05:00
Nathan Neulinger
93786d9ebd Update Dockerfile.buildx 2022-07-06 08:54:19 -05:00
Nathan Neulinger
a6dbb580c9 Update Dockerfile.alpine 2022-07-06 08:53:58 -05:00
Nathan Neulinger
e62678abdb Update Dockerfile 2022-07-06 08:53:18 -05:00
Daniel García
af50eae604 Merge pull request #2586 from jjlin/password-hint-config
Add `password_hints_allowed` config option
2022-07-01 16:31:56 +02:00
Jeremy Lin
cb4f6aa7f6 Pin a specific version of Rust
The latest version (1.62.0) that was just released includes Clippy changes
(https://github.com/rust-lang/rust-clippy/issues/9014) that break the build.
2022-06-30 23:56:33 -07:00
Jeremy Lin
5e13b1a7cb Add password_hints_allowed config option
Disabling password hints is mainly useful for admins who are concerned that
their users might provide password hints that are too revealing.
2022-06-30 20:46:17 -07:00
Daniel García
60b339f450 Update included web vault to v2022.5.2 2022-06-26 22:04:45 +02:00
Daniel García
f71c779860 Merge branch 'BlackDex-log-level-adjustment' into main 2022-06-26 21:54:54 +02:00
Daniel García
221a11de9b Merge branch 'log-level-adjustment' of https://github.com/BlackDex/vaultwarden into BlackDex-log-level-adjustment 2022-06-26 21:54:48 +02:00
Daniel García
794483c10d Merge branch 'BlackDex-fix-issue-2570' into main 2022-06-26 21:54:27 +02:00
Daniel García
c9934ccdb7 Merge branch 'fix-issue-2570' of https://github.com/BlackDex/vaultwarden into BlackDex-fix-issue-2570 2022-06-26 21:54:22 +02:00
Daniel García
54729f3c1e Merge branch 'BlackDex-optimize-icon-html-parsing' into main 2022-06-26 21:54:10 +02:00
Daniel García
f1a86acb98 Merge branch 'optimize-icon-html-parsing' of https://github.com/BlackDex/vaultwarden into BlackDex-optimize-icon-html-parsing 2022-06-26 21:54:03 +02:00
Daniel García
6b6ea3c8bf Merge branch 'BlackDex-fix-issue-2566' into main 2022-06-26 21:53:06 +02:00
Daniel García
bf403fee7d Merge branch 'fix-issue-2566' of https://github.com/BlackDex/vaultwarden into BlackDex-fix-issue-2566 2022-06-26 21:52:59 +02:00
Daniel García
5cd920cf6f Merge branch 'BlackDex-allow-firefox-relay' into main 2022-06-26 21:51:50 +02:00
BlackDex
45d3b479bc Small change in log-level for better debugging
Regarding some recent issues with sending attachments, but previously
also some changes to the API for example which could cause a `400` error
it just returned that there is something wrong, but not to much details
on what exactly.

To help with getting a bit more detailed information, we should set the
log-level for `_` to at least `Warn`.
2022-06-26 14:49:26 +02:00
BlackDex
c7a752b01d Update dep's and small improvements on favicons
- Updated dependencies (html5gum for favicon downloading)
  * Also openssl, time, jsonwebtoken and r2d2
- Small optimizations on downloading favicons.
  It now only emits tokens/tags which needs to be parsed, all others are
  being skipped. This prevents unneeded items within the for-loop being
  parsed.
2022-06-25 11:29:08 +02:00
BlackDex
099d359628 Fix identicons not always working
Fixes #2570
Reverted the `defer` option for these scripts, seems to cause some
issues in some situations.
2022-06-22 16:38:16 +02:00
BlackDex
006a2aacbb Allow FireFox relay in CSP.
This PR is needed for https://github.com/dani-garcia/bw_web_builds/pull/71
Without this the web-vault will refuse to make calls to the FireFox Relay API.

Also fixed a small issue with the pre-commit config.
2022-06-22 16:30:31 +02:00
BlackDex
b71d9dd53e Fix for issue #2566
This PR fixes #2566
If Organizational syncs returned a FolderId it would cause the web-vault
to hide the cipher because there is a FolderId set. Upstream seems to
not return FolderId and Favorite. When set to null/false it will behave
the same.

In this PR I have added a new CipherSyncType enum to select which type
of sync to execute, and return an empty list for both Folders and Favorites if this is for Orgs.
This also reduces the database load a bit since it will not execute those queries.
2022-06-21 17:36:07 +02:00
Daniel García
887e320e7f Merge pull request #2555 from jjlin/global-domains
Sync global_domains.json
2022-06-15 20:44:35 +02:00
Daniel García
d7c18fd86e Merge pull request #2556 from binlab/patch-1
A little depreciation change
2022-06-15 20:44:14 +02:00
Daniel García
7566f3db3e Merge pull request #2543 from BlackDex/update-and-fixes
Updated deps and misc fixes and updates
2022-06-15 20:43:26 +02:00
BlackDex
5d05ec58be Updated deps and misc fixes and updates
- Updated some Rust dependencies
- Fixed an issue with CSP header, this was not configured correctly
- Prevent sending CSP and Frame headers for the MFA connector.html files.
  Else some clients will fail to handle these protocols.
- Add `unsafe-inline` for `script-src` only to the CSP for the Admin Interface
- Updated JavaScript and CSS files for the Admin interface
- Changed the layout for showing overridden settings, better visible now.
- Made the version check cachable to prevent hitting the Github API rate limits
- Hide the `database_url` as if it is a password in the Admin Interface
  Else for MariaDB/MySQL or PostgreSQL this was plain text.
- Fixed an issue that pressing enter on the SMTP Test would save the config.
  resolves #2542
- Prevent user names larger then 50 characters
  resolves #2419
2022-06-14 14:51:51 +02:00
Mark
d9a452f558 A little depreciation change 2022-06-13 13:56:41 +03:00
Jeremy Lin
dec03b3dc0 Sync global_domains.json to bitwarden/server@194b76c (HealthCare.gov) 2022-06-12 20:15:20 -07:00
Jeremy Lin
85950bdc0b Sync global_domains.json to bitwarden/server@496c9a5 (Proton) 2022-06-12 20:14:30 -07:00
Daniel García
f95bd3bb04 Update pico-args 2022-06-04 19:16:36 +02:00
BlackDex
e33b8fab34 Re-Base, Update crates and small change. 2022-06-04 19:14:14 +02:00
Daniel García
b00fbf153e Fix clippy lint and remove unused log 2022-06-04 19:13:58 +02:00
Daniel García
0de5919a16 Fix incorrect pings sent, and respond to pings from the client 2022-06-04 19:13:58 +02:00
Daniel García
699777be9e use dashmap in icons blacklist regex 2022-06-04 19:13:58 +02:00
Daniel García
16ff49d712 Move to job_scheduler_ng 2022-06-04 19:13:57 +02:00
Daniel García
54c78cf06d Migrate old ws crate to tungstenite, which is async and also removes over 20 old dependencies 2022-06-04 19:13:39 +02:00
Daniel García
303eaabeea Merge branch 'paolobarbolini-lettre-improvements' into main 2022-06-04 19:13:12 +02:00
Daniel García
6b6f5b8d04 Merge branch 'lettre-improvements' of https://github.com/paolobarbolini/vaultwarden into paolobarbolini-lettre-improvements 2022-06-04 19:10:51 +02:00
Daniel García
0c18a7e306 Merge branch 'paolobarbolini-lettre-rc7' into main 2022-06-04 19:09:11 +02:00
Daniel García
a23a38080b Merge branch 'lettre-rc7' of https://github.com/paolobarbolini/vaultwarden into paolobarbolini-lettre-rc7 2022-06-04 19:09:03 +02:00
Daniel García
316ca66a4b Merge branch 'Lowaiz-add_disabled_member_to_json_user' into main 2022-06-04 19:08:23 +02:00
Daniel García
2f71a01877 Merge branch 'add_disabled_member_to_json_user' of https://github.com/Lowaiz/vaultwarden into Lowaiz-add_disabled_member_to_json_user 2022-06-04 19:08:15 +02:00
Daniel García
d5cfbfc71d Update web vault to v2022.05.0 2022-06-04 19:07:15 +02:00
Paolo Barbolini
12612da75e Remove manual IDN handling 2022-06-04 19:02:51 +02:00
Paolo Barbolini
68ec5f2a18 Use MultiPart::alternative_plain_html instead of manual impl 2022-06-04 14:53:27 +02:00
Paolo Barbolini
00670450df Bump lettre to 0.10.0-rc.7 2022-06-04 14:47:26 +02:00
Lyonel Martinez
dbd95e08e9 Adding "UserEnabled" and "CreatedAt" member to the json output of a User in the admin/users and admin/users/<ID> web routes. 2022-06-02 15:13:58 +02:00
Daniel García
3713f2d134 Merge pull request #2507 from BlackDex/fix-persisten-volume-check
Fix persistent volume check
2022-05-28 14:56:47 +02:00
BlackDex
a85a250dfd Fix persistent volume check
It seemed there were some issues building the cross-platform images.
This PR fixes #2501 so building the containers will work again.
2022-05-28 09:31:09 +02:00
Daniel García
5845ed2c92 Merge pull request #2501 from BlackDex/add-persistent-volume-check-docker
Add a persistent volume check.
2022-05-27 19:41:42 +02:00
BlackDex
40ed505581 Add a persistent volume check.
This will add a persistent volume check to make sure when running
containers someone is using a volume for persistent storage.

This check can be bypassed if someone configures
`I_REALLY_WANT_VOLATILE_STORAGE=true` as an environment variable.

This should prevent issues like #2493 .
2022-05-26 09:39:56 +02:00
Daniel García
bf0b8d9968 Merge pull request #2491 from BlackDex/issue-2490
Fix armv6 issue with bullseye images
2022-05-24 15:46:34 +02:00
Daniel García
d0a7437dbd Merge pull request #2489 from fox34/update-env-template
Add TMP_FOLDER to .env.template
2022-05-24 15:33:22 +02:00
BlackDex
21b433c5d7 Fix armv6 issue with bullseye images
It looks like the armv6 bullseye images are missing a symlink to the
dynamic linker. The previous buster images had this symlink there,
bullseye does not.

This PR fixes adds that symlink again for only the Debian armv6 build.

Resolves #2490
2022-05-24 15:25:51 +02:00
fox34
7c89bc619a Add TMP_FOLDER to .env.template 2022-05-24 09:38:16 +02:00
Daniel García
0d3daa9fc6 Remove recommendation to set ROCKET_CLI_COLORS to off
The value is now a boolean so setting it to off will cause an error
2022-05-23 20:19:29 +02:00
55 changed files with 10434 additions and 9493 deletions

View File

@@ -43,6 +43,7 @@
# ICON_CACHE_FOLDER=data/icon_cache
# ATTACHMENTS_FOLDER=data/attachments
# SENDS_FOLDER=data/sends
# TMP_FOLDER=data/tmp
## Templates data folder, by default uses embedded templates
## Check source code to see the format
@@ -116,12 +117,10 @@
# LOG_TIMESTAMP_FORMAT="%Y-%m-%d %H:%M:%S.%3f"
## Logging to file
## It's recommended to also set 'ROCKET_CLI_COLORS=off'
# LOG_FILE=/path/to/log
## Logging to Syslog
## This requires extended logging
## It's recommended to also set 'ROCKET_CLI_COLORS=off'
# USE_SYSLOG=false
## Log level
@@ -271,6 +270,9 @@
## The change only applies when the password is changed
# PASSWORD_ITERATIONS=100000
## Controls whether users can set password hints. This setting applies globally to all users.
# PASSWORD_HINTS_ALLOWED=true
## Controls whether a password hint should be shown directly in the web page if
## SMTP service is not configured. Not recommended for publicly-accessible instances
## as this provides unauthenticated access to potentially sensitive data.
@@ -281,7 +283,7 @@
## It's recommended to configure this value, otherwise certain functionality might not work,
## like attachment downloads, email links and U2F.
## For U2F to work, the server must use HTTPS, you can use Let's Encrypt for free certs
# DOMAIN=https://bw.domain.tld:8443
# DOMAIN=https://vw.domain.tld:8443
## Allowed iframe ancestors (Know the risks!)
## https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/frame-ancestors

View File

@@ -8,7 +8,6 @@ on:
- "migrations/**"
- "Cargo.*"
- "build.rs"
- "diesel.toml"
- "rust-toolchain"
pull_request:
paths:
@@ -17,7 +16,6 @@ on:
- "migrations/**"
- "Cargo.*"
- "build.rs"
- "diesel.toml"
- "rust-toolchain"
jobs:

View File

@@ -1,7 +1,7 @@
---
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.2.0
rev: v4.3.0
hooks:
- id: check-yaml
- id: check-json
@@ -26,7 +26,8 @@ repos:
entry: cargo test
language: system
args: ["--features", "sqlite,mysql,postgresql,enable_mimalloc", "--"]
types: [rust]
types_or: [rust, file]
files: (Cargo.toml|Cargo.lock|.*\.rs$)
pass_filenames: false
- id: cargo-clippy
name: cargo clippy
@@ -34,5 +35,6 @@ repos:
entry: cargo clippy
language: system
args: ["--features", "sqlite,mysql,postgresql,enable_mimalloc", "--", "-D", "warnings"]
types: [rust]
types_or: [rust, file]
files: (Cargo.toml|Cargo.lock|.*\.rs$)
pass_filenames: false

1285
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -3,7 +3,7 @@ name = "vaultwarden"
version = "1.0.0"
authors = ["Daniel García <dani-garcia@users.noreply.github.com>"]
edition = "2021"
rust-version = "1.60"
rust-version = "1.59"
resolver = "2"
repository = "https://github.com/dani-garcia/vaultwarden"
@@ -37,15 +37,15 @@ syslog = "6.0.1" # Needs to be v4 until fern is updated
# Logging
log = "0.4.17"
fern = { version = "0.6.1", features = ["syslog-6"] }
tracing = { version = "0.1.34", features = ["log"] } # Needed to have lettre and webauthn-rs trace logging to work
tracing = { version = "0.1.35", features = ["log"] } # Needed to have lettre and webauthn-rs trace logging to work
backtrace = "0.3.65" # Logging panics to logfile instead stderr only
backtrace = "0.3.66" # Logging panics to logfile instead stderr only
# A `dotenv` implementation for Rust
dotenvy = { version = "0.15.1", default-features = false }
# Lazy initialization
once_cell = "1.10.0"
once_cell = "1.13.0"
# Numerical libraries
num-traits = "0.2.15"
@@ -55,17 +55,17 @@ num-derive = "0.3.3"
rocket = { version = "0.5.0-rc.2", features = ["tls", "json"], default-features = false }
# WebSockets libraries
ws = { version = "0.11.1", package = "parity-ws" }
tokio-tungstenite = "0.17.2"
rmpv = "1.0.0" # MessagePack library
chashmap = "2.2.2" # Concurrent hashmap implementation
dashmap = "5.3.4" # Concurrent hashmap implementation
# Async futures
futures = "0.3.21"
tokio = { version = "1.18.2", features = ["rt-multi-thread", "fs", "io-util", "parking_lot", "time"] }
tokio = { version = "1.20.0", features = ["rt-multi-thread", "fs", "io-util", "parking_lot", "time"] }
# A generic serialization/deserialization framework
serde = { version = "1.0.137", features = ["derive"] }
serde_json = "1.0.81"
serde = { version = "1.0.139", features = ["derive"] }
serde_json = "1.0.82"
# A safe, extensible ORM and Query builder
diesel = { version = "1.4.8", features = ["chrono", "r2d2"] }
@@ -75,28 +75,28 @@ diesel_migrations = "1.4.0"
libsqlite3-sys = { version = "0.22.2", features = ["bundled"], optional = true }
# Crypto-related libraries
rand = "0.8.5"
rand = { version = "0.8.5", features = ["small_rng"] }
ring = "0.16.20"
# UUID generation
uuid = { version = "1.0.0", features = ["v4"] }
uuid = { version = "1.1.2", features = ["v4"] }
# Date and time libraries
chrono = { version = "0.4.19", features = ["clock", "serde"], default-features = false }
chrono-tz = "0.6.1"
time = "0.3.9"
time = "0.3.11"
# Job scheduler
job_scheduler = "1.2.1"
job_scheduler_ng = "2.0.1"
# Data encoding library Hex/Base32/Base64
data-encoding = "2.3.2"
# JWT library
jsonwebtoken = "8.1.0"
jsonwebtoken = "8.1.1"
# TOTP library
totp-lite = "1.0.3"
totp-lite = "2.0.0"
# Yubico Library
yubico = { version = "0.11.0", features = ["online-tokio"], default-features = false }
@@ -107,33 +107,32 @@ webauthn-rs = "0.3.2"
# Handling of URL's for WebAuthn
url = "2.2.2"
# Email libraries
idna = "0.2.3" # Punycode conversion
lettre = { version = "0.10.0-rc.6", features = ["smtp-transport", "builder", "serde", "native-tls", "hostname", "tracing"], default-features = false }
# Email librariese-Base, Update crates and small change.
lettre = { version = "0.10.0", features = ["smtp-transport", "builder", "serde", "tokio1-native-tls", "hostname", "tracing", "tokio1"], default-features = false }
percent-encoding = "2.1.0" # URL encoding library used for URL's in the emails
# Template library
handlebars = { version = "4.2.2", features = ["dir_source"] }
handlebars = { version = "4.3.2", features = ["dir_source"] }
# HTTP client
reqwest = { version = "0.11.10", features = ["stream", "json", "gzip", "brotli", "socks", "cookies", "trust-dns"] }
reqwest = { version = "0.11.11", features = ["stream", "json", "gzip", "brotli", "socks", "cookies", "trust-dns"] }
# For favicon extraction from main website
html5gum = "0.4.0"
regex = { version = "1.5.5", features = ["std", "perf", "unicode-perl"], default-features = false }
html5gum = "0.5.2"
regex = { version = "1.6.0", features = ["std", "perf", "unicode-perl"], default-features = false }
data-url = "0.1.1"
bytes = "1.1.0"
cached = "0.34.0"
cached = "0.36.0"
# Used for custom short lived cookie jar during favicon extraction
cookie = "0.16.0"
cookie_store = "0.16.0"
cookie_store = "0.16.1"
# Used by U2F, JWT and Postgres
openssl = "0.10.40"
openssl = "0.10.41"
# CLI argument parsing
pico-args = "0.4.2"
pico-args = "0.5.0"
# Macro ident concatenation
paste = "1.0.7"
@@ -146,15 +145,6 @@ ctrlc = { version = "3.2.2", features = ["termination"] }
# Mainly used for the musl builds, since the default musl malloc is very slow
mimalloc = { version = "0.1.29", features = ["secure"], default-features = false, optional = true }
[patch.crates-io]
# 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).
# 2022-05-04: Forked/Updated the job_scheduler again use the latest dependencies and some fixes.
job_scheduler = { git = 'https://github.com/BlackDex/job_scheduler', rev = '9100fc596a083fd9c0b560f8f11f108e0a19d07e' }
# Strip debuginfo from the release builds
# Also enable thin LTO for some optimizations
[profile.release]

View File

@@ -59,8 +59,8 @@
# https://docs.docker.com/develop/develop-images/multistage-build/
# https://whitfin.io/speeding-up-rust-docker-builds/
####################### VAULT BUILD IMAGE #######################
{% set vault_version = "2.28.1" %}
{% set vault_image_digest = "sha256:df7f12b1e22bf0dfc1b6b6f46921b4e9e649561931ba65357c1eb1963514b3b5" %}
{% set vault_version = "v2022.6.2" %}
{% set vault_image_digest = "sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70" %}
# The web-vault digest specifies a particular web-vault build on Docker Hub.
# 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
@@ -70,13 +70,13 @@
# - 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.
# - From the command line:
# $ docker pull vaultwarden/web-vault:v{{ vault_version }}
# $ docker image inspect --format "{{ '{{' }}.RepoDigests}}" vaultwarden/web-vault:v{{ vault_version }}
# $ docker pull vaultwarden/web-vault:{{ vault_version }}
# $ docker image inspect --format "{{ '{{' }}.RepoDigests}}" vaultwarden/web-vault:{{ vault_version }}
# [vaultwarden/web-vault@{{ vault_image_digest }}]
#
# - Conversely, to get the tag name from the digest:
# $ docker image inspect --format "{{ '{{' }}.RepoTags}}" vaultwarden/web-vault@{{ vault_image_digest }}
# [vaultwarden/web-vault:v{{ vault_version }}]
# [vaultwarden/web-vault:{{ vault_version }}]
#
FROM vaultwarden/web-vault@{{ vault_image_digest }} as vault
@@ -93,12 +93,6 @@ ENV DEBIAN_FRONTEND=noninteractive \
CARGO_HOME="/root/.cargo" \
USER="root"
{# {% if "alpine" not in target_file and "buildx" in target_file %}
# Debian based Buildx builds can use some special apt caching to speedup building.
# By default Debian based images have some rules to keep docker builds clean, we need to remove this.
# See: https://hub.docker.com/r/docker/dockerfile
RUN rm -f /etc/apt/apt.conf.d/docker-clean; echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' > /etc/apt/apt.conf.d/keep-cache
{% endif %} #}
# Create CARGO_HOME folder and don't download rust docs
RUN {{ mount_rust_cache -}} mkdir -pv "${CARGO_HOME}" \
@@ -187,6 +181,14 @@ RUN touch src/main.rs
# hadolint ignore=DL3059
RUN {{ mount_rust_cache -}} cargo build --features ${DB} --release{{ package_arch_target_param }}
# Create a special empty file which we check within the application.
# If this file exists, then we exit Vaultwarden to prevent data loss when someone forgets to use volumes.
# If you really really want to use volatile storage you can set the env `I_REALLY_WANT_VOLATILE_STORAGE=true`
# This file should disappear if a volume is mounted on-top of this using a docker volume.
# We run this in the build image and copy it over, because the runtime image could be missing some executables.
# hadolint ignore=DL3059
RUN touch /vaultwarden_docker_persistent_volume_check
######################## RUNTIME IMAGE ########################
# Create a new stage with a minimal image
# because we already have a binary built
@@ -227,6 +229,14 @@ RUN mkdir /data \
&& rm -rf /var/lib/apt/lists/*
{% endif %}
{% if "armv6" in target_file and "alpine" not in target_file %}
# In the Balena Bullseye images for armv6/rpi-debian there is a missing symlink.
# This symlink was there in the buster images, and for some reason this is needed.
# hadolint ignore=DL3059
RUN ln -v -s /lib/ld-linux-armhf.so.3 /lib/ld-linux.so.3
{% endif -%}
{% if "amd64" not in target_file %}
# hadolint ignore=DL3059
RUN [ "cross-build-end" ]
@@ -240,6 +250,7 @@ EXPOSE 3012
# and the binary from the "build" stage to the current stage
WORKDIR /
COPY --from=vault /web-vault ./web-vault
COPY --from=build /vaultwarden_docker_persistent_volume_check /data/vaultwarden_docker_persistent_volume_check
{% if package_arch_target is defined %}
COPY --from=build /app/target/{{ package_arch_target }}/release/vaultwarden .
{% else %}

View File

@@ -16,15 +16,15 @@
# - 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.
# - From the command line:
# $ docker pull vaultwarden/web-vault:v2.28.1
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2.28.1
# [vaultwarden/web-vault@sha256:df7f12b1e22bf0dfc1b6b6f46921b4e9e649561931ba65357c1eb1963514b3b5]
# $ docker pull vaultwarden/web-vault:v2022.6.2
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2022.6.2
# [vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70]
#
# - Conversely, to get the tag name from the digest:
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:df7f12b1e22bf0dfc1b6b6f46921b4e9e649561931ba65357c1eb1963514b3b5
# [vaultwarden/web-vault:v2.28.1]
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70
# [vaultwarden/web-vault:v2022.6.2]
#
FROM vaultwarden/web-vault@sha256:df7f12b1e22bf0dfc1b6b6f46921b4e9e649561931ba65357c1eb1963514b3b5 as vault
FROM vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70 as vault
########################## BUILD IMAGE ##########################
FROM rust:1.61-bullseye as build
@@ -84,6 +84,14 @@ RUN touch src/main.rs
# hadolint ignore=DL3059
RUN cargo build --features ${DB} --release
# Create a special empty file which we check within the application.
# If this file exists, then we exit Vaultwarden to prevent data loss when someone forgets to use volumes.
# If you really really want to use volatile storage you can set the env `I_REALLY_WANT_VOLATILE_STORAGE=true`
# This file should disappear if a volume is mounted on-top of this using a docker volume.
# We run this in the build image and copy it over, because the runtime image could be missing some executables.
# hadolint ignore=DL3059
RUN touch /vaultwarden_docker_persistent_volume_check
######################## RUNTIME IMAGE ########################
# Create a new stage with a minimal image
# because we already have a binary built
@@ -116,6 +124,7 @@ EXPOSE 3012
# and the binary from the "build" stage to the current stage
WORKDIR /
COPY --from=vault /web-vault ./web-vault
COPY --from=build /vaultwarden_docker_persistent_volume_check /data/vaultwarden_docker_persistent_volume_check
COPY --from=build /app/target/release/vaultwarden .
COPY docker/healthcheck.sh /healthcheck.sh

View File

@@ -16,15 +16,15 @@
# - 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.
# - From the command line:
# $ docker pull vaultwarden/web-vault:v2.28.1
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2.28.1
# [vaultwarden/web-vault@sha256:df7f12b1e22bf0dfc1b6b6f46921b4e9e649561931ba65357c1eb1963514b3b5]
# $ docker pull vaultwarden/web-vault:v2022.6.2
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2022.6.2
# [vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70]
#
# - Conversely, to get the tag name from the digest:
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:df7f12b1e22bf0dfc1b6b6f46921b4e9e649561931ba65357c1eb1963514b3b5
# [vaultwarden/web-vault:v2.28.1]
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70
# [vaultwarden/web-vault:v2022.6.2]
#
FROM vaultwarden/web-vault@sha256:df7f12b1e22bf0dfc1b6b6f46921b4e9e649561931ba65357c1eb1963514b3b5 as vault
FROM vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70 as vault
########################## BUILD IMAGE ##########################
FROM blackdex/rust-musl:x86_64-musl-stable-1.61.0 as build
@@ -78,6 +78,14 @@ RUN touch src/main.rs
# hadolint ignore=DL3059
RUN cargo build --features ${DB} --release --target=x86_64-unknown-linux-musl
# Create a special empty file which we check within the application.
# If this file exists, then we exit Vaultwarden to prevent data loss when someone forgets to use volumes.
# If you really really want to use volatile storage you can set the env `I_REALLY_WANT_VOLATILE_STORAGE=true`
# This file should disappear if a volume is mounted on-top of this using a docker volume.
# We run this in the build image and copy it over, because the runtime image could be missing some executables.
# hadolint ignore=DL3059
RUN touch /vaultwarden_docker_persistent_volume_check
######################## RUNTIME IMAGE ########################
# Create a new stage with a minimal image
# because we already have a binary built
@@ -108,6 +116,7 @@ EXPOSE 3012
# and the binary from the "build" stage to the current stage
WORKDIR /
COPY --from=vault /web-vault ./web-vault
COPY --from=build /vaultwarden_docker_persistent_volume_check /data/vaultwarden_docker_persistent_volume_check
COPY --from=build /app/target/x86_64-unknown-linux-musl/release/vaultwarden .
COPY docker/healthcheck.sh /healthcheck.sh

View File

@@ -16,15 +16,15 @@
# - 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.
# - From the command line:
# $ docker pull vaultwarden/web-vault:v2.28.1
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2.28.1
# [vaultwarden/web-vault@sha256:df7f12b1e22bf0dfc1b6b6f46921b4e9e649561931ba65357c1eb1963514b3b5]
# $ docker pull vaultwarden/web-vault:v2022.6.2
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2022.6.2
# [vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70]
#
# - Conversely, to get the tag name from the digest:
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:df7f12b1e22bf0dfc1b6b6f46921b4e9e649561931ba65357c1eb1963514b3b5
# [vaultwarden/web-vault:v2.28.1]
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70
# [vaultwarden/web-vault:v2022.6.2]
#
FROM vaultwarden/web-vault@sha256:df7f12b1e22bf0dfc1b6b6f46921b4e9e649561931ba65357c1eb1963514b3b5 as vault
FROM vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70 as vault
########################## BUILD IMAGE ##########################
FROM rust:1.61-bullseye as build
@@ -84,6 +84,14 @@ RUN touch src/main.rs
# hadolint ignore=DL3059
RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/.cargo/registry cargo build --features ${DB} --release
# Create a special empty file which we check within the application.
# If this file exists, then we exit Vaultwarden to prevent data loss when someone forgets to use volumes.
# If you really really want to use volatile storage you can set the env `I_REALLY_WANT_VOLATILE_STORAGE=true`
# This file should disappear if a volume is mounted on-top of this using a docker volume.
# We run this in the build image and copy it over, because the runtime image could be missing some executables.
# hadolint ignore=DL3059
RUN touch /vaultwarden_docker_persistent_volume_check
######################## RUNTIME IMAGE ########################
# Create a new stage with a minimal image
# because we already have a binary built
@@ -116,6 +124,7 @@ EXPOSE 3012
# and the binary from the "build" stage to the current stage
WORKDIR /
COPY --from=vault /web-vault ./web-vault
COPY --from=build /vaultwarden_docker_persistent_volume_check /data/vaultwarden_docker_persistent_volume_check
COPY --from=build /app/target/release/vaultwarden .
COPY docker/healthcheck.sh /healthcheck.sh

View File

@@ -16,15 +16,15 @@
# - 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.
# - From the command line:
# $ docker pull vaultwarden/web-vault:v2.28.1
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2.28.1
# [vaultwarden/web-vault@sha256:df7f12b1e22bf0dfc1b6b6f46921b4e9e649561931ba65357c1eb1963514b3b5]
# $ docker pull vaultwarden/web-vault:v2022.6.2
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2022.6.2
# [vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70]
#
# - Conversely, to get the tag name from the digest:
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:df7f12b1e22bf0dfc1b6b6f46921b4e9e649561931ba65357c1eb1963514b3b5
# [vaultwarden/web-vault:v2.28.1]
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70
# [vaultwarden/web-vault:v2022.6.2]
#
FROM vaultwarden/web-vault@sha256:df7f12b1e22bf0dfc1b6b6f46921b4e9e649561931ba65357c1eb1963514b3b5 as vault
FROM vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70 as vault
########################## BUILD IMAGE ##########################
FROM blackdex/rust-musl:x86_64-musl-stable-1.61.0 as build
@@ -78,6 +78,14 @@ RUN touch src/main.rs
# hadolint ignore=DL3059
RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/.cargo/registry cargo build --features ${DB} --release --target=x86_64-unknown-linux-musl
# Create a special empty file which we check within the application.
# If this file exists, then we exit Vaultwarden to prevent data loss when someone forgets to use volumes.
# If you really really want to use volatile storage you can set the env `I_REALLY_WANT_VOLATILE_STORAGE=true`
# This file should disappear if a volume is mounted on-top of this using a docker volume.
# We run this in the build image and copy it over, because the runtime image could be missing some executables.
# hadolint ignore=DL3059
RUN touch /vaultwarden_docker_persistent_volume_check
######################## RUNTIME IMAGE ########################
# Create a new stage with a minimal image
# because we already have a binary built
@@ -108,6 +116,7 @@ EXPOSE 3012
# and the binary from the "build" stage to the current stage
WORKDIR /
COPY --from=vault /web-vault ./web-vault
COPY --from=build /vaultwarden_docker_persistent_volume_check /data/vaultwarden_docker_persistent_volume_check
COPY --from=build /app/target/x86_64-unknown-linux-musl/release/vaultwarden .
COPY docker/healthcheck.sh /healthcheck.sh

View File

@@ -16,15 +16,15 @@
# - 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.
# - From the command line:
# $ docker pull vaultwarden/web-vault:v2.28.1
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2.28.1
# [vaultwarden/web-vault@sha256:df7f12b1e22bf0dfc1b6b6f46921b4e9e649561931ba65357c1eb1963514b3b5]
# $ docker pull vaultwarden/web-vault:v2022.6.2
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2022.6.2
# [vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70]
#
# - Conversely, to get the tag name from the digest:
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:df7f12b1e22bf0dfc1b6b6f46921b4e9e649561931ba65357c1eb1963514b3b5
# [vaultwarden/web-vault:v2.28.1]
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70
# [vaultwarden/web-vault:v2022.6.2]
#
FROM vaultwarden/web-vault@sha256:df7f12b1e22bf0dfc1b6b6f46921b4e9e649561931ba65357c1eb1963514b3b5 as vault
FROM vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70 as vault
########################## BUILD IMAGE ##########################
FROM rust:1.61-bullseye as build
@@ -104,6 +104,14 @@ RUN touch src/main.rs
# hadolint ignore=DL3059
RUN cargo build --features ${DB} --release --target=aarch64-unknown-linux-gnu
# Create a special empty file which we check within the application.
# If this file exists, then we exit Vaultwarden to prevent data loss when someone forgets to use volumes.
# If you really really want to use volatile storage you can set the env `I_REALLY_WANT_VOLATILE_STORAGE=true`
# This file should disappear if a volume is mounted on-top of this using a docker volume.
# We run this in the build image and copy it over, because the runtime image could be missing some executables.
# hadolint ignore=DL3059
RUN touch /vaultwarden_docker_persistent_volume_check
######################## RUNTIME IMAGE ########################
# Create a new stage with a minimal image
# because we already have a binary built
@@ -140,6 +148,7 @@ EXPOSE 3012
# and the binary from the "build" stage to the current stage
WORKDIR /
COPY --from=vault /web-vault ./web-vault
COPY --from=build /vaultwarden_docker_persistent_volume_check /data/vaultwarden_docker_persistent_volume_check
COPY --from=build /app/target/aarch64-unknown-linux-gnu/release/vaultwarden .
COPY docker/healthcheck.sh /healthcheck.sh

View File

@@ -16,15 +16,15 @@
# - 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.
# - From the command line:
# $ docker pull vaultwarden/web-vault:v2.28.1
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2.28.1
# [vaultwarden/web-vault@sha256:df7f12b1e22bf0dfc1b6b6f46921b4e9e649561931ba65357c1eb1963514b3b5]
# $ docker pull vaultwarden/web-vault:v2022.6.2
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2022.6.2
# [vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70]
#
# - Conversely, to get the tag name from the digest:
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:df7f12b1e22bf0dfc1b6b6f46921b4e9e649561931ba65357c1eb1963514b3b5
# [vaultwarden/web-vault:v2.28.1]
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70
# [vaultwarden/web-vault:v2022.6.2]
#
FROM vaultwarden/web-vault@sha256:df7f12b1e22bf0dfc1b6b6f46921b4e9e649561931ba65357c1eb1963514b3b5 as vault
FROM vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70 as vault
########################## BUILD IMAGE ##########################
FROM blackdex/rust-musl:aarch64-musl-stable-1.61.0 as build
@@ -78,6 +78,14 @@ RUN touch src/main.rs
# hadolint ignore=DL3059
RUN cargo build --features ${DB} --release --target=aarch64-unknown-linux-musl
# Create a special empty file which we check within the application.
# If this file exists, then we exit Vaultwarden to prevent data loss when someone forgets to use volumes.
# If you really really want to use volatile storage you can set the env `I_REALLY_WANT_VOLATILE_STORAGE=true`
# This file should disappear if a volume is mounted on-top of this using a docker volume.
# We run this in the build image and copy it over, because the runtime image could be missing some executables.
# hadolint ignore=DL3059
RUN touch /vaultwarden_docker_persistent_volume_check
######################## RUNTIME IMAGE ########################
# Create a new stage with a minimal image
# because we already have a binary built
@@ -112,6 +120,7 @@ EXPOSE 3012
# and the binary from the "build" stage to the current stage
WORKDIR /
COPY --from=vault /web-vault ./web-vault
COPY --from=build /vaultwarden_docker_persistent_volume_check /data/vaultwarden_docker_persistent_volume_check
COPY --from=build /app/target/aarch64-unknown-linux-musl/release/vaultwarden .
COPY docker/healthcheck.sh /healthcheck.sh

View File

@@ -16,15 +16,15 @@
# - 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.
# - From the command line:
# $ docker pull vaultwarden/web-vault:v2.28.1
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2.28.1
# [vaultwarden/web-vault@sha256:df7f12b1e22bf0dfc1b6b6f46921b4e9e649561931ba65357c1eb1963514b3b5]
# $ docker pull vaultwarden/web-vault:v2022.6.2
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2022.6.2
# [vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70]
#
# - Conversely, to get the tag name from the digest:
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:df7f12b1e22bf0dfc1b6b6f46921b4e9e649561931ba65357c1eb1963514b3b5
# [vaultwarden/web-vault:v2.28.1]
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70
# [vaultwarden/web-vault:v2022.6.2]
#
FROM vaultwarden/web-vault@sha256:df7f12b1e22bf0dfc1b6b6f46921b4e9e649561931ba65357c1eb1963514b3b5 as vault
FROM vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70 as vault
########################## BUILD IMAGE ##########################
FROM rust:1.61-bullseye as build
@@ -104,6 +104,14 @@ RUN touch src/main.rs
# hadolint ignore=DL3059
RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/.cargo/registry cargo build --features ${DB} --release --target=aarch64-unknown-linux-gnu
# Create a special empty file which we check within the application.
# If this file exists, then we exit Vaultwarden to prevent data loss when someone forgets to use volumes.
# If you really really want to use volatile storage you can set the env `I_REALLY_WANT_VOLATILE_STORAGE=true`
# This file should disappear if a volume is mounted on-top of this using a docker volume.
# We run this in the build image and copy it over, because the runtime image could be missing some executables.
# hadolint ignore=DL3059
RUN touch /vaultwarden_docker_persistent_volume_check
######################## RUNTIME IMAGE ########################
# Create a new stage with a minimal image
# because we already have a binary built
@@ -140,6 +148,7 @@ EXPOSE 3012
# and the binary from the "build" stage to the current stage
WORKDIR /
COPY --from=vault /web-vault ./web-vault
COPY --from=build /vaultwarden_docker_persistent_volume_check /data/vaultwarden_docker_persistent_volume_check
COPY --from=build /app/target/aarch64-unknown-linux-gnu/release/vaultwarden .
COPY docker/healthcheck.sh /healthcheck.sh

View File

@@ -16,15 +16,15 @@
# - 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.
# - From the command line:
# $ docker pull vaultwarden/web-vault:v2.28.1
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2.28.1
# [vaultwarden/web-vault@sha256:df7f12b1e22bf0dfc1b6b6f46921b4e9e649561931ba65357c1eb1963514b3b5]
# $ docker pull vaultwarden/web-vault:v2022.6.2
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2022.6.2
# [vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70]
#
# - Conversely, to get the tag name from the digest:
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:df7f12b1e22bf0dfc1b6b6f46921b4e9e649561931ba65357c1eb1963514b3b5
# [vaultwarden/web-vault:v2.28.1]
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70
# [vaultwarden/web-vault:v2022.6.2]
#
FROM vaultwarden/web-vault@sha256:df7f12b1e22bf0dfc1b6b6f46921b4e9e649561931ba65357c1eb1963514b3b5 as vault
FROM vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70 as vault
########################## BUILD IMAGE ##########################
FROM blackdex/rust-musl:aarch64-musl-stable-1.61.0 as build
@@ -78,6 +78,14 @@ RUN touch src/main.rs
# hadolint ignore=DL3059
RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/.cargo/registry cargo build --features ${DB} --release --target=aarch64-unknown-linux-musl
# Create a special empty file which we check within the application.
# If this file exists, then we exit Vaultwarden to prevent data loss when someone forgets to use volumes.
# If you really really want to use volatile storage you can set the env `I_REALLY_WANT_VOLATILE_STORAGE=true`
# This file should disappear if a volume is mounted on-top of this using a docker volume.
# We run this in the build image and copy it over, because the runtime image could be missing some executables.
# hadolint ignore=DL3059
RUN touch /vaultwarden_docker_persistent_volume_check
######################## RUNTIME IMAGE ########################
# Create a new stage with a minimal image
# because we already have a binary built
@@ -112,6 +120,7 @@ EXPOSE 3012
# and the binary from the "build" stage to the current stage
WORKDIR /
COPY --from=vault /web-vault ./web-vault
COPY --from=build /vaultwarden_docker_persistent_volume_check /data/vaultwarden_docker_persistent_volume_check
COPY --from=build /app/target/aarch64-unknown-linux-musl/release/vaultwarden .
COPY docker/healthcheck.sh /healthcheck.sh

View File

@@ -16,15 +16,15 @@
# - 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.
# - From the command line:
# $ docker pull vaultwarden/web-vault:v2.28.1
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2.28.1
# [vaultwarden/web-vault@sha256:df7f12b1e22bf0dfc1b6b6f46921b4e9e649561931ba65357c1eb1963514b3b5]
# $ docker pull vaultwarden/web-vault:v2022.6.2
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2022.6.2
# [vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70]
#
# - Conversely, to get the tag name from the digest:
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:df7f12b1e22bf0dfc1b6b6f46921b4e9e649561931ba65357c1eb1963514b3b5
# [vaultwarden/web-vault:v2.28.1]
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70
# [vaultwarden/web-vault:v2022.6.2]
#
FROM vaultwarden/web-vault@sha256:df7f12b1e22bf0dfc1b6b6f46921b4e9e649561931ba65357c1eb1963514b3b5 as vault
FROM vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70 as vault
########################## BUILD IMAGE ##########################
FROM rust:1.61-bullseye as build
@@ -104,6 +104,14 @@ RUN touch src/main.rs
# hadolint ignore=DL3059
RUN cargo build --features ${DB} --release --target=arm-unknown-linux-gnueabi
# Create a special empty file which we check within the application.
# If this file exists, then we exit Vaultwarden to prevent data loss when someone forgets to use volumes.
# If you really really want to use volatile storage you can set the env `I_REALLY_WANT_VOLATILE_STORAGE=true`
# This file should disappear if a volume is mounted on-top of this using a docker volume.
# We run this in the build image and copy it over, because the runtime image could be missing some executables.
# hadolint ignore=DL3059
RUN touch /vaultwarden_docker_persistent_volume_check
######################## RUNTIME IMAGE ########################
# Create a new stage with a minimal image
# because we already have a binary built
@@ -129,6 +137,11 @@ RUN mkdir /data \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
# In the Balena Bullseye images for armv6/rpi-debian there is a missing symlink.
# This symlink was there in the buster images, and for some reason this is needed.
# hadolint ignore=DL3059
RUN ln -v -s /lib/ld-linux-armhf.so.3 /lib/ld-linux.so.3
# hadolint ignore=DL3059
RUN [ "cross-build-end" ]
@@ -140,6 +153,7 @@ EXPOSE 3012
# and the binary from the "build" stage to the current stage
WORKDIR /
COPY --from=vault /web-vault ./web-vault
COPY --from=build /vaultwarden_docker_persistent_volume_check /data/vaultwarden_docker_persistent_volume_check
COPY --from=build /app/target/arm-unknown-linux-gnueabi/release/vaultwarden .
COPY docker/healthcheck.sh /healthcheck.sh

View File

@@ -16,15 +16,15 @@
# - 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.
# - From the command line:
# $ docker pull vaultwarden/web-vault:v2.28.1
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2.28.1
# [vaultwarden/web-vault@sha256:df7f12b1e22bf0dfc1b6b6f46921b4e9e649561931ba65357c1eb1963514b3b5]
# $ docker pull vaultwarden/web-vault:v2022.6.2
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2022.6.2
# [vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70]
#
# - Conversely, to get the tag name from the digest:
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:df7f12b1e22bf0dfc1b6b6f46921b4e9e649561931ba65357c1eb1963514b3b5
# [vaultwarden/web-vault:v2.28.1]
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70
# [vaultwarden/web-vault:v2022.6.2]
#
FROM vaultwarden/web-vault@sha256:df7f12b1e22bf0dfc1b6b6f46921b4e9e649561931ba65357c1eb1963514b3b5 as vault
FROM vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70 as vault
########################## BUILD IMAGE ##########################
FROM blackdex/rust-musl:arm-musleabi-stable-1.61.0 as build
@@ -80,6 +80,14 @@ RUN touch src/main.rs
# hadolint ignore=DL3059
RUN cargo build --features ${DB} --release --target=arm-unknown-linux-musleabi
# Create a special empty file which we check within the application.
# If this file exists, then we exit Vaultwarden to prevent data loss when someone forgets to use volumes.
# If you really really want to use volatile storage you can set the env `I_REALLY_WANT_VOLATILE_STORAGE=true`
# This file should disappear if a volume is mounted on-top of this using a docker volume.
# We run this in the build image and copy it over, because the runtime image could be missing some executables.
# hadolint ignore=DL3059
RUN touch /vaultwarden_docker_persistent_volume_check
######################## RUNTIME IMAGE ########################
# Create a new stage with a minimal image
# because we already have a binary built
@@ -114,6 +122,7 @@ EXPOSE 3012
# and the binary from the "build" stage to the current stage
WORKDIR /
COPY --from=vault /web-vault ./web-vault
COPY --from=build /vaultwarden_docker_persistent_volume_check /data/vaultwarden_docker_persistent_volume_check
COPY --from=build /app/target/arm-unknown-linux-musleabi/release/vaultwarden .
COPY docker/healthcheck.sh /healthcheck.sh

View File

@@ -16,15 +16,15 @@
# - 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.
# - From the command line:
# $ docker pull vaultwarden/web-vault:v2.28.1
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2.28.1
# [vaultwarden/web-vault@sha256:df7f12b1e22bf0dfc1b6b6f46921b4e9e649561931ba65357c1eb1963514b3b5]
# $ docker pull vaultwarden/web-vault:v2022.6.2
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2022.6.2
# [vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70]
#
# - Conversely, to get the tag name from the digest:
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:df7f12b1e22bf0dfc1b6b6f46921b4e9e649561931ba65357c1eb1963514b3b5
# [vaultwarden/web-vault:v2.28.1]
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70
# [vaultwarden/web-vault:v2022.6.2]
#
FROM vaultwarden/web-vault@sha256:df7f12b1e22bf0dfc1b6b6f46921b4e9e649561931ba65357c1eb1963514b3b5 as vault
FROM vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70 as vault
########################## BUILD IMAGE ##########################
FROM rust:1.61-bullseye as build
@@ -104,6 +104,14 @@ RUN touch src/main.rs
# hadolint ignore=DL3059
RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/.cargo/registry cargo build --features ${DB} --release --target=arm-unknown-linux-gnueabi
# Create a special empty file which we check within the application.
# If this file exists, then we exit Vaultwarden to prevent data loss when someone forgets to use volumes.
# If you really really want to use volatile storage you can set the env `I_REALLY_WANT_VOLATILE_STORAGE=true`
# This file should disappear if a volume is mounted on-top of this using a docker volume.
# We run this in the build image and copy it over, because the runtime image could be missing some executables.
# hadolint ignore=DL3059
RUN touch /vaultwarden_docker_persistent_volume_check
######################## RUNTIME IMAGE ########################
# Create a new stage with a minimal image
# because we already have a binary built
@@ -129,6 +137,11 @@ RUN mkdir /data \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
# In the Balena Bullseye images for armv6/rpi-debian there is a missing symlink.
# This symlink was there in the buster images, and for some reason this is needed.
# hadolint ignore=DL3059
RUN ln -v -s /lib/ld-linux-armhf.so.3 /lib/ld-linux.so.3
# hadolint ignore=DL3059
RUN [ "cross-build-end" ]
@@ -140,6 +153,7 @@ EXPOSE 3012
# and the binary from the "build" stage to the current stage
WORKDIR /
COPY --from=vault /web-vault ./web-vault
COPY --from=build /vaultwarden_docker_persistent_volume_check /data/vaultwarden_docker_persistent_volume_check
COPY --from=build /app/target/arm-unknown-linux-gnueabi/release/vaultwarden .
COPY docker/healthcheck.sh /healthcheck.sh

View File

@@ -16,15 +16,15 @@
# - 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.
# - From the command line:
# $ docker pull vaultwarden/web-vault:v2.28.1
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2.28.1
# [vaultwarden/web-vault@sha256:df7f12b1e22bf0dfc1b6b6f46921b4e9e649561931ba65357c1eb1963514b3b5]
# $ docker pull vaultwarden/web-vault:v2022.6.2
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2022.6.2
# [vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70]
#
# - Conversely, to get the tag name from the digest:
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:df7f12b1e22bf0dfc1b6b6f46921b4e9e649561931ba65357c1eb1963514b3b5
# [vaultwarden/web-vault:v2.28.1]
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70
# [vaultwarden/web-vault:v2022.6.2]
#
FROM vaultwarden/web-vault@sha256:df7f12b1e22bf0dfc1b6b6f46921b4e9e649561931ba65357c1eb1963514b3b5 as vault
FROM vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70 as vault
########################## BUILD IMAGE ##########################
FROM blackdex/rust-musl:arm-musleabi-stable-1.61.0 as build
@@ -80,6 +80,14 @@ RUN touch src/main.rs
# hadolint ignore=DL3059
RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/.cargo/registry cargo build --features ${DB} --release --target=arm-unknown-linux-musleabi
# Create a special empty file which we check within the application.
# If this file exists, then we exit Vaultwarden to prevent data loss when someone forgets to use volumes.
# If you really really want to use volatile storage you can set the env `I_REALLY_WANT_VOLATILE_STORAGE=true`
# This file should disappear if a volume is mounted on-top of this using a docker volume.
# We run this in the build image and copy it over, because the runtime image could be missing some executables.
# hadolint ignore=DL3059
RUN touch /vaultwarden_docker_persistent_volume_check
######################## RUNTIME IMAGE ########################
# Create a new stage with a minimal image
# because we already have a binary built
@@ -114,6 +122,7 @@ EXPOSE 3012
# and the binary from the "build" stage to the current stage
WORKDIR /
COPY --from=vault /web-vault ./web-vault
COPY --from=build /vaultwarden_docker_persistent_volume_check /data/vaultwarden_docker_persistent_volume_check
COPY --from=build /app/target/arm-unknown-linux-musleabi/release/vaultwarden .
COPY docker/healthcheck.sh /healthcheck.sh

View File

@@ -16,15 +16,15 @@
# - 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.
# - From the command line:
# $ docker pull vaultwarden/web-vault:v2.28.1
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2.28.1
# [vaultwarden/web-vault@sha256:df7f12b1e22bf0dfc1b6b6f46921b4e9e649561931ba65357c1eb1963514b3b5]
# $ docker pull vaultwarden/web-vault:v2022.6.2
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2022.6.2
# [vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70]
#
# - Conversely, to get the tag name from the digest:
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:df7f12b1e22bf0dfc1b6b6f46921b4e9e649561931ba65357c1eb1963514b3b5
# [vaultwarden/web-vault:v2.28.1]
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70
# [vaultwarden/web-vault:v2022.6.2]
#
FROM vaultwarden/web-vault@sha256:df7f12b1e22bf0dfc1b6b6f46921b4e9e649561931ba65357c1eb1963514b3b5 as vault
FROM vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70 as vault
########################## BUILD IMAGE ##########################
FROM rust:1.61-bullseye as build
@@ -104,6 +104,14 @@ RUN touch src/main.rs
# hadolint ignore=DL3059
RUN cargo build --features ${DB} --release --target=armv7-unknown-linux-gnueabihf
# Create a special empty file which we check within the application.
# If this file exists, then we exit Vaultwarden to prevent data loss when someone forgets to use volumes.
# If you really really want to use volatile storage you can set the env `I_REALLY_WANT_VOLATILE_STORAGE=true`
# This file should disappear if a volume is mounted on-top of this using a docker volume.
# We run this in the build image and copy it over, because the runtime image could be missing some executables.
# hadolint ignore=DL3059
RUN touch /vaultwarden_docker_persistent_volume_check
######################## RUNTIME IMAGE ########################
# Create a new stage with a minimal image
# because we already have a binary built
@@ -140,6 +148,7 @@ EXPOSE 3012
# and the binary from the "build" stage to the current stage
WORKDIR /
COPY --from=vault /web-vault ./web-vault
COPY --from=build /vaultwarden_docker_persistent_volume_check /data/vaultwarden_docker_persistent_volume_check
COPY --from=build /app/target/armv7-unknown-linux-gnueabihf/release/vaultwarden .
COPY docker/healthcheck.sh /healthcheck.sh

View File

@@ -16,15 +16,15 @@
# - 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.
# - From the command line:
# $ docker pull vaultwarden/web-vault:v2.28.1
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2.28.1
# [vaultwarden/web-vault@sha256:df7f12b1e22bf0dfc1b6b6f46921b4e9e649561931ba65357c1eb1963514b3b5]
# $ docker pull vaultwarden/web-vault:v2022.6.2
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2022.6.2
# [vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70]
#
# - Conversely, to get the tag name from the digest:
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:df7f12b1e22bf0dfc1b6b6f46921b4e9e649561931ba65357c1eb1963514b3b5
# [vaultwarden/web-vault:v2.28.1]
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70
# [vaultwarden/web-vault:v2022.6.2]
#
FROM vaultwarden/web-vault@sha256:df7f12b1e22bf0dfc1b6b6f46921b4e9e649561931ba65357c1eb1963514b3b5 as vault
FROM vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70 as vault
########################## BUILD IMAGE ##########################
FROM blackdex/rust-musl:armv7-musleabihf-stable-1.61.0 as build
@@ -78,6 +78,14 @@ RUN touch src/main.rs
# hadolint ignore=DL3059
RUN cargo build --features ${DB} --release --target=armv7-unknown-linux-musleabihf
# Create a special empty file which we check within the application.
# If this file exists, then we exit Vaultwarden to prevent data loss when someone forgets to use volumes.
# If you really really want to use volatile storage you can set the env `I_REALLY_WANT_VOLATILE_STORAGE=true`
# This file should disappear if a volume is mounted on-top of this using a docker volume.
# We run this in the build image and copy it over, because the runtime image could be missing some executables.
# hadolint ignore=DL3059
RUN touch /vaultwarden_docker_persistent_volume_check
######################## RUNTIME IMAGE ########################
# Create a new stage with a minimal image
# because we already have a binary built
@@ -112,6 +120,7 @@ EXPOSE 3012
# and the binary from the "build" stage to the current stage
WORKDIR /
COPY --from=vault /web-vault ./web-vault
COPY --from=build /vaultwarden_docker_persistent_volume_check /data/vaultwarden_docker_persistent_volume_check
COPY --from=build /app/target/armv7-unknown-linux-musleabihf/release/vaultwarden .
COPY docker/healthcheck.sh /healthcheck.sh

View File

@@ -16,15 +16,15 @@
# - 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.
# - From the command line:
# $ docker pull vaultwarden/web-vault:v2.28.1
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2.28.1
# [vaultwarden/web-vault@sha256:df7f12b1e22bf0dfc1b6b6f46921b4e9e649561931ba65357c1eb1963514b3b5]
# $ docker pull vaultwarden/web-vault:v2022.6.2
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2022.6.2
# [vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70]
#
# - Conversely, to get the tag name from the digest:
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:df7f12b1e22bf0dfc1b6b6f46921b4e9e649561931ba65357c1eb1963514b3b5
# [vaultwarden/web-vault:v2.28.1]
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70
# [vaultwarden/web-vault:v2022.6.2]
#
FROM vaultwarden/web-vault@sha256:df7f12b1e22bf0dfc1b6b6f46921b4e9e649561931ba65357c1eb1963514b3b5 as vault
FROM vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70 as vault
########################## BUILD IMAGE ##########################
FROM rust:1.61-bullseye as build
@@ -104,6 +104,14 @@ RUN touch src/main.rs
# hadolint ignore=DL3059
RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/.cargo/registry cargo build --features ${DB} --release --target=armv7-unknown-linux-gnueabihf
# Create a special empty file which we check within the application.
# If this file exists, then we exit Vaultwarden to prevent data loss when someone forgets to use volumes.
# If you really really want to use volatile storage you can set the env `I_REALLY_WANT_VOLATILE_STORAGE=true`
# This file should disappear if a volume is mounted on-top of this using a docker volume.
# We run this in the build image and copy it over, because the runtime image could be missing some executables.
# hadolint ignore=DL3059
RUN touch /vaultwarden_docker_persistent_volume_check
######################## RUNTIME IMAGE ########################
# Create a new stage with a minimal image
# because we already have a binary built
@@ -140,6 +148,7 @@ EXPOSE 3012
# and the binary from the "build" stage to the current stage
WORKDIR /
COPY --from=vault /web-vault ./web-vault
COPY --from=build /vaultwarden_docker_persistent_volume_check /data/vaultwarden_docker_persistent_volume_check
COPY --from=build /app/target/armv7-unknown-linux-gnueabihf/release/vaultwarden .
COPY docker/healthcheck.sh /healthcheck.sh

View File

@@ -16,15 +16,15 @@
# - 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.
# - From the command line:
# $ docker pull vaultwarden/web-vault:v2.28.1
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2.28.1
# [vaultwarden/web-vault@sha256:df7f12b1e22bf0dfc1b6b6f46921b4e9e649561931ba65357c1eb1963514b3b5]
# $ docker pull vaultwarden/web-vault:v2022.6.2
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2022.6.2
# [vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70]
#
# - Conversely, to get the tag name from the digest:
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:df7f12b1e22bf0dfc1b6b6f46921b4e9e649561931ba65357c1eb1963514b3b5
# [vaultwarden/web-vault:v2.28.1]
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70
# [vaultwarden/web-vault:v2022.6.2]
#
FROM vaultwarden/web-vault@sha256:df7f12b1e22bf0dfc1b6b6f46921b4e9e649561931ba65357c1eb1963514b3b5 as vault
FROM vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70 as vault
########################## BUILD IMAGE ##########################
FROM blackdex/rust-musl:armv7-musleabihf-stable-1.61.0 as build
@@ -78,6 +78,14 @@ RUN touch src/main.rs
# hadolint ignore=DL3059
RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/.cargo/registry cargo build --features ${DB} --release --target=armv7-unknown-linux-musleabihf
# Create a special empty file which we check within the application.
# If this file exists, then we exit Vaultwarden to prevent data loss when someone forgets to use volumes.
# If you really really want to use volatile storage you can set the env `I_REALLY_WANT_VOLATILE_STORAGE=true`
# This file should disappear if a volume is mounted on-top of this using a docker volume.
# We run this in the build image and copy it over, because the runtime image could be missing some executables.
# hadolint ignore=DL3059
RUN touch /vaultwarden_docker_persistent_volume_check
######################## RUNTIME IMAGE ########################
# Create a new stage with a minimal image
# because we already have a binary built
@@ -112,6 +120,7 @@ EXPOSE 3012
# and the binary from the "build" stage to the current stage
WORKDIR /
COPY --from=vault /web-vault ./web-vault
COPY --from=build /vaultwarden_docker_persistent_volume_check /data/vaultwarden_docker_persistent_volume_check
COPY --from=build /app/target/armv7-unknown-linux-musleabihf/release/vaultwarden .
COPY docker/healthcheck.sh /healthcheck.sh

View File

@@ -1 +1 @@
stable
1.61.0

View File

@@ -79,6 +79,7 @@ fn admin_disabled() -> &'static str {
const COOKIE_NAME: &str = "VW_ADMIN";
const ADMIN_PATH: &str = "/admin";
const DT_FMT: &str = "%Y-%m-%d %H:%M:%S %Z";
const BASE_TEMPLATE: &str = "admin/base";
@@ -275,7 +276,7 @@ async fn invite_user(data: Json<InviteData>, _token: AdminToken, conn: DbConn) -
async fn _generate_invite(user: &User, conn: &DbConn) -> EmptyResult {
if CONFIG.mail_enabled() {
mail::send_invite(&user.email, &user.uuid, None, None, &CONFIG.invitation_org_name(), None)
mail::send_invite(&user.email, &user.uuid, None, None, &CONFIG.invitation_org_name(), None).await
} else {
let invitation = Invitation::new(user.email.clone());
invitation.save(conn).await
@@ -289,11 +290,11 @@ async fn invite_user(data: Json<InviteData>, _token: AdminToken, conn: DbConn) -
}
#[post("/test/smtp", data = "<data>")]
fn test_smtp(data: Json<InviteData>, _token: AdminToken) -> EmptyResult {
async fn test_smtp(data: Json<InviteData>, _token: AdminToken) -> EmptyResult {
let data: InviteData = data.into_inner();
if CONFIG.mail_enabled() {
mail::send_test(&data.email)
mail::send_test(&data.email).await
} else {
err!("Mail is not enabled")
}
@@ -310,7 +311,10 @@ async fn get_users_json(_token: AdminToken, conn: DbConn) -> Json<Value> {
let users_json = stream::iter(User::get_all(&conn).await)
.then(|u| async {
let u = u; // Move out this single variable
u.to_json(&conn).await
let mut usr = u.to_json(&conn).await;
usr["UserEnabled"] = json!(u.enabled);
usr["CreatedAt"] = json!(format_naive_datetime_local(&u.created_at, DT_FMT));
usr
})
.collect::<Vec<Value>>()
.await;
@@ -320,8 +324,6 @@ async fn get_users_json(_token: AdminToken, conn: DbConn) -> Json<Value> {
#[get("/users/overview")]
async fn users_overview(_token: AdminToken, conn: DbConn) -> ApiResult<Html<String>> {
const DT_FMT: &str = "%Y-%m-%d %H:%M:%S %Z";
let users_json = stream::iter(User::get_all(&conn).await)
.then(|u| async {
let u = u; // Move out this single variable
@@ -346,9 +348,11 @@ async fn users_overview(_token: AdminToken, conn: DbConn) -> ApiResult<Html<Stri
#[get("/users/<uuid>")]
async fn get_user_json(uuid: String, _token: AdminToken, conn: DbConn) -> JsonResult {
let user = get_user_or_404(&uuid, &conn).await?;
Ok(Json(user.to_json(&conn).await))
let u = get_user_or_404(&uuid, &conn).await?;
let mut usr = u.to_json(&conn).await;
usr["UserEnabled"] = json!(u.enabled);
usr["CreatedAt"] = json!(format_naive_datetime_local(&u.created_at, DT_FMT));
Ok(Json(usr))
}
#[post("/users/<uuid>/delete")]
@@ -423,7 +427,7 @@ async fn update_user_org_type(data: Json<UserOrgTypeData>, _token: AdminToken, c
}
}
user_to_edit.atype = new_type as i32;
user_to_edit.atype = new_type;
user_to_edit.save(&conn).await
}
@@ -487,41 +491,14 @@ async fn has_http_access() -> bool {
}
}
#[get("/diagnostics")]
async fn diagnostics(_token: AdminToken, ip_header: IpHeader, conn: DbConn) -> ApiResult<Html<String>> {
use crate::util::read_file_string;
use chrono::prelude::*;
use std::net::ToSocketAddrs;
// Get current running versions
let web_vault_version: WebVaultVersion =
match read_file_string(&format!("{}/{}", CONFIG.web_vault_folder(), "vw-version.json")) {
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
let running_within_docker = is_running_in_docker();
let has_http_access = has_http_access().await;
let uses_proxy = env::var_os("HTTP_PROXY").is_some()
|| env::var_os("http_proxy").is_some()
|| env::var_os("HTTPS_PROXY").is_some()
|| env::var_os("https_proxy").is_some();
// Check if we are able to resolve DNS entries
let dns_resolved = match ("github.com", 0).to_socket_addrs().map(|mut i| i.next()) {
Ok(Some(a)) => a.ip().to_string(),
_ => "Could not resolve domain name.".to_string(),
};
use cached::proc_macro::cached;
/// Cache this function to prevent API call rate limit. Github only allows 60 requests per hour, and we use 3 here already.
/// It will cache this function for 300 seconds (5 minutes) which should prevent the exhaustion of the rate limit.
#[cached(time = 300, sync_writes = true)]
async fn get_release_info(has_http_access: bool, running_within_docker: bool) -> (String, String, String) {
// If the HTTP Check failed, do not even attempt to check for new versions since we were not able to connect with github.com anyway.
// 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 {
if has_http_access {
info!("Running get_release_info!!");
(
match get_github_api::<GitRelease>("https://api.github.com/repos/dani-garcia/vaultwarden/releases/latest")
.await
@@ -554,8 +531,43 @@ async fn diagnostics(_token: AdminToken, ip_header: IpHeader, conn: DbConn) -> A
)
} else {
("-".to_string(), "-".to_string(), "-".to_string())
}
}
#[get("/diagnostics")]
async fn diagnostics(_token: AdminToken, ip_header: IpHeader, conn: DbConn) -> ApiResult<Html<String>> {
use chrono::prelude::*;
use std::net::ToSocketAddrs;
// Get current running versions
let web_vault_version: WebVaultVersion =
match std::fs::read_to_string(&format!("{}/{}", CONFIG.web_vault_folder(), "vw-version.json")) {
Ok(s) => serde_json::from_str(&s)?,
_ => match std::fs::read_to_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
let running_within_docker = is_running_in_docker();
let has_http_access = has_http_access().await;
let uses_proxy = env::var_os("HTTP_PROXY").is_some()
|| env::var_os("http_proxy").is_some()
|| env::var_os("HTTPS_PROXY").is_some()
|| env::var_os("https_proxy").is_some();
// Check if we are able to resolve DNS entries
let dns_resolved = match ("github.com", 0).to_socket_addrs().map(|mut i| i.next()) {
Ok(Some(a)) => a.ip().to_string(),
_ => "Could not resolve domain name.".to_string(),
};
let (latest_release, latest_commit, latest_web_build) =
get_release_info(has_http_access, running_within_docker).await;
let ip_header_name = match &ip_header.0 {
Some(h) => h,
_ => "",

View File

@@ -62,11 +62,42 @@ struct KeysData {
PublicKey: String,
}
/// Trims whitespace from password hints, and converts blank password hints to `None`.
fn clean_password_hint(password_hint: &Option<String>) -> Option<String> {
match password_hint {
None => None,
Some(h) => match h.trim() {
"" => None,
ht => Some(ht.to_string()),
},
}
}
fn enforce_password_hint_setting(password_hint: &Option<String>) -> EmptyResult {
if password_hint.is_some() && !CONFIG.password_hints_allowed() {
err!("Password hints have been disabled by the administrator. Remove the hint and try again.");
}
Ok(())
}
#[post("/accounts/register", data = "<data>")]
async fn register(data: JsonUpcase<RegisterData>, conn: DbConn) -> EmptyResult {
let data: RegisterData = data.into_inner().data;
let email = data.Email.to_lowercase();
// Check if the length of the username exceeds 50 characters (Same is Upstream Bitwarden)
// This also prevents issues with very long usernames causing to large JWT's. See #2419
if let Some(ref name) = data.Name {
if name.len() > 50 {
err!("The field Name must be a string with a maximum length of 50.");
}
}
// Check against the password hint setting here so if it fails, the user
// can retry without losing their invitation below.
let password_hint = clean_password_hint(&data.MasterPasswordHint);
enforce_password_hint_setting(&password_hint)?;
let mut user = match User::find_by_mail(&email, &conn).await {
Some(user) => {
if !user.password_hash.is_empty() {
@@ -123,16 +154,13 @@ async fn register(data: JsonUpcase<RegisterData>, conn: DbConn) -> EmptyResult {
user.set_password(&data.MasterPasswordHash, None);
user.akey = data.Key;
user.password_hint = password_hint;
// Add extra fields if present
if let Some(name) = data.Name {
user.name = name;
}
if let Some(hint) = data.MasterPasswordHint {
user.password_hint = Some(hint);
}
if let Some(keys) = data.Keys {
user.private_key = Some(keys.EncryptedPrivateKey);
user.public_key = Some(keys.PublicKey);
@@ -140,12 +168,12 @@ async fn register(data: JsonUpcase<RegisterData>, conn: DbConn) -> EmptyResult {
if CONFIG.mail_enabled() {
if CONFIG.signups_verify() {
if let Err(e) = mail::send_welcome_must_verify(&user.email, &user.uuid) {
if let Err(e) = mail::send_welcome_must_verify(&user.email, &user.uuid).await {
error!("Error sending welcome email: {:#?}", e);
}
user.last_verifying_at = Some(user.created_at);
} else if let Err(e) = mail::send_welcome(&user.email) {
} else if let Err(e) = mail::send_welcome(&user.email).await {
error!("Error sending welcome email: {:#?}", e);
}
}
@@ -176,13 +204,17 @@ async fn put_profile(data: JsonUpcase<ProfileData>, headers: Headers, conn: DbCo
async fn post_profile(data: JsonUpcase<ProfileData>, headers: Headers, conn: DbConn) -> JsonResult {
let data: ProfileData = data.into_inner().data;
let mut user = headers.user;
// Check if the length of the username exceeds 50 characters (Same is Upstream Bitwarden)
// This also prevents issues with very long usernames causing to large JWT's. See #2419
if data.Name.len() > 50 {
err!("The field Name must be a string with a maximum length of 50.");
}
let mut user = headers.user;
user.name = data.Name;
user.password_hint = match data.MasterPasswordHint {
Some(ref h) if h.is_empty() => None,
_ => data.MasterPasswordHint,
};
user.password_hint = clean_password_hint(&data.MasterPasswordHint);
enforce_password_hint_setting(&user.password_hint)?;
user.save(&conn).await?;
Ok(Json(user.to_json(&conn).await))
}
@@ -384,7 +416,7 @@ async fn post_email_token(data: JsonUpcase<EmailTokenData>, headers: Headers, co
let token = crypto::generate_email_token(6);
if CONFIG.mail_enabled() {
if let Err(e) = mail::send_change_email(&data.NewEmail, &token) {
if let Err(e) = mail::send_change_email(&data.NewEmail, &token).await {
error!("Error sending change-email email: {:#?}", e);
}
}
@@ -453,14 +485,14 @@ async fn post_email(data: JsonUpcase<ChangeEmailData>, headers: Headers, conn: D
}
#[post("/accounts/verify-email")]
fn post_verify_email(headers: Headers) -> EmptyResult {
async fn post_verify_email(headers: Headers) -> EmptyResult {
let user = headers.user;
if !CONFIG.mail_enabled() {
err!("Cannot verify email address");
}
if let Err(e) = mail::send_verify_email(&user.email, &user.uuid) {
if let Err(e) = mail::send_verify_email(&user.email, &user.uuid).await {
error!("Error sending verify_email email: {:#?}", e);
}
@@ -512,7 +544,7 @@ async fn post_delete_recover(data: JsonUpcase<DeleteRecoverData>, conn: DbConn)
if CONFIG.mail_enabled() {
if let Some(user) = User::find_by_mail(&data.Email, &conn).await {
if let Err(e) = mail::send_delete_account(&user.email, &user.uuid) {
if let Err(e) = mail::send_delete_account(&user.email, &user.uuid).await {
error!("Error sending delete account email: {:#?}", e);
}
}
@@ -612,7 +644,7 @@ async fn password_hint(data: JsonUpcase<PasswordHintData>, conn: DbConn) -> Empt
Some(user) => {
let hint: Option<String> = user.password_hint;
if CONFIG.mail_enabled() {
mail::send_password_hint(email, hint)?;
mail::send_password_hint(email, hint).await?;
Ok(())
} else if let Some(hint) = hint {
err!(format!("Your password hint is: {}", hint));

View File

@@ -1,6 +1,7 @@
use std::collections::{HashMap, HashSet};
use chrono::{NaiveDateTime, Utc};
use futures::{stream, stream::StreamExt};
use rocket::fs::TempFile;
use rocket::serde::json::Json;
use rocket::{
@@ -17,7 +18,7 @@ use crate::{
CONFIG,
};
use futures::{stream, stream::StreamExt};
use super::folders::FolderData;
pub fn routes() -> Vec<Route> {
// Note that many routes have an `admin` variant; this seems to be
@@ -104,7 +105,7 @@ async fn sync(data: SyncData, headers: Headers, conn: DbConn) -> Json<Value> {
// Get all ciphers which are visible by the user
let ciphers = Cipher::find_by_user_visible(&headers.user.uuid, &conn).await;
let cipher_sync_data = CipherSyncData::new(&headers.user.uuid, &ciphers, &conn).await;
let cipher_sync_data = CipherSyncData::new(&headers.user.uuid, &ciphers, CipherSyncType::User, &conn).await;
// Lets generate the ciphers_json using all the gathered info
let ciphers_json: Vec<Value> = stream::iter(ciphers)
@@ -154,7 +155,7 @@ async fn sync(data: SyncData, headers: Headers, conn: DbConn) -> Json<Value> {
#[get("/ciphers")]
async fn get_ciphers(headers: Headers, conn: DbConn) -> Json<Value> {
let ciphers = Cipher::find_by_user_visible(&headers.user.uuid, &conn).await;
let cipher_sync_data = CipherSyncData::new(&headers.user.uuid, &ciphers, &conn).await;
let cipher_sync_data = CipherSyncData::new(&headers.user.uuid, &ciphers, CipherSyncType::User, &conn).await;
let ciphers_json = stream::iter(ciphers)
.then(|c| async {
@@ -212,7 +213,7 @@ pub struct CipherData {
Card = 3,
Identity = 4
*/
pub Type: i32, // TODO: Change this to NumberOrString
pub Type: i32,
pub Name: String,
Notes: Option<String>,
Fields: Option<Value>,
@@ -229,8 +230,9 @@ pub struct CipherData {
PasswordHistory: Option<Value>,
// These are used during key rotation
// 'Attachments' is unused, contains map of {id: filename}
#[serde(rename = "Attachments")]
_Attachments: Option<Value>, // Unused, contains map of {id: filename}
_Attachments: Option<Value>,
Attachments2: Option<HashMap<String, Attachments2Data>>,
// The revision datetime (in ISO 8601 format) of the client's local copy
@@ -464,14 +466,12 @@ pub async fn update_cipher_from_data(
cipher.set_favorite(data.Favorite, &headers.user.uuid, conn).await?;
if ut != UpdateType::None {
nt.send_cipher_update(ut, cipher, &cipher.update_users_revision(conn).await);
nt.send_cipher_update(ut, cipher, &cipher.update_users_revision(conn).await).await;
}
Ok(())
}
use super::folders::FolderData;
#[derive(Deserialize)]
#[allow(non_snake_case)]
struct ImportData {
@@ -527,7 +527,7 @@ async fn post_ciphers_import(
let mut user = headers.user;
user.update_revision(&conn).await?;
nt.send_user_update(UpdateType::Vault, &user);
nt.send_user_update(UpdateType::Vault, &user).await;
Ok(())
}
@@ -913,8 +913,8 @@ async fn save_attachment(
// In the v2 API, the attachment record has already been created,
// so the size limit needs to be adjusted to account for that.
let size_adjust = match &attachment {
None => 0, // Legacy API
Some(a) => a.file_size as i64, // v2 API
None => 0, // Legacy API
Some(a) => i64::from(a.file_size), // v2 API
};
let size_limit = if let Some(ref user_uuid) = cipher.user_uuid {
@@ -998,9 +998,11 @@ async fn save_attachment(
attachment.save(&conn).await.expect("Error saving attachment");
}
data.data.persist_to(file_path).await?;
if let Err(_err) = data.data.persist_to(&file_path).await {
data.data.move_copy_to(file_path).await?
}
nt.send_cipher_update(UpdateType::CipherUpdate, &cipher, &cipher.update_users_revision(&conn).await);
nt.send_cipher_update(UpdateType::CipherUpdate, &cipher, &cipher.update_users_revision(&conn).await).await;
Ok((cipher, conn))
}
@@ -1266,7 +1268,7 @@ async fn move_cipher_selected(
// Move cipher
cipher.move_to_folder(data.FolderId.clone(), &user_uuid, &conn).await?;
nt.send_cipher_update(UpdateType::CipherUpdate, &cipher, &[user_uuid.clone()]);
nt.send_cipher_update(UpdateType::CipherUpdate, &cipher, &[user_uuid.clone()]).await;
}
Ok(())
@@ -1313,7 +1315,7 @@ async fn delete_all(
Some(user_org) => {
if user_org.atype == UserOrgType::Owner {
Cipher::delete_all_by_organization(&org_data.org_id, &conn).await?;
nt.send_user_update(UpdateType::Vault, &user);
nt.send_user_update(UpdateType::Vault, &user).await;
Ok(())
} else {
err!("You don't have permission to purge the organization vault");
@@ -1334,7 +1336,7 @@ async fn delete_all(
}
user.update_revision(&conn).await?;
nt.send_user_update(UpdateType::Vault, &user);
nt.send_user_update(UpdateType::Vault, &user).await;
Ok(())
}
}
@@ -1359,10 +1361,10 @@ async fn _delete_cipher_by_uuid(
if soft_delete {
cipher.deleted_at = Some(Utc::now().naive_utc());
cipher.save(conn).await?;
nt.send_cipher_update(UpdateType::CipherUpdate, &cipher, &cipher.update_users_revision(conn).await);
nt.send_cipher_update(UpdateType::CipherUpdate, &cipher, &cipher.update_users_revision(conn).await).await;
} else {
cipher.delete(conn).await?;
nt.send_cipher_update(UpdateType::CipherDelete, &cipher, &cipher.update_users_revision(conn).await);
nt.send_cipher_update(UpdateType::CipherDelete, &cipher, &cipher.update_users_revision(conn).await).await;
}
Ok(())
@@ -1407,7 +1409,7 @@ async fn _restore_cipher_by_uuid(uuid: &str, headers: &Headers, conn: &DbConn, n
cipher.deleted_at = None;
cipher.save(conn).await?;
nt.send_cipher_update(UpdateType::CipherUpdate, &cipher, &cipher.update_users_revision(conn).await);
nt.send_cipher_update(UpdateType::CipherUpdate, &cipher, &cipher.update_users_revision(conn).await).await;
Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, None, conn).await))
}
@@ -1469,7 +1471,7 @@ async fn _delete_cipher_attachment_by_id(
// Delete attachment
attachment.delete(conn).await?;
nt.send_cipher_update(UpdateType::CipherUpdate, &cipher, &cipher.update_users_revision(conn).await);
nt.send_cipher_update(UpdateType::CipherUpdate, &cipher, &cipher.update_users_revision(conn).await).await;
Ok(())
}
@@ -1486,25 +1488,39 @@ pub struct CipherSyncData {
pub user_collections: HashMap<String, CollectionUser>,
}
pub enum CipherSyncType {
User,
Organization,
}
impl CipherSyncData {
pub async fn new(user_uuid: &str, ciphers: &Vec<Cipher>, conn: &DbConn) -> Self {
pub async fn new(user_uuid: &str, ciphers: &Vec<Cipher>, sync_type: CipherSyncType, conn: &DbConn) -> Self {
// Generate a list of Cipher UUID's to be used during a query filter with an eq_any.
let cipher_uuids = stream::iter(ciphers).map(|c| c.uuid.to_string()).collect::<Vec<String>>().await;
let cipher_uuids = stream::iter(ciphers).map(|c| c.uuid.clone()).collect::<Vec<String>>().await;
let mut cipher_folders: HashMap<String, String> = HashMap::new();
let mut cipher_favorites: HashSet<String> = HashSet::new();
match sync_type {
// User Sync supports Folders and Favorits
CipherSyncType::User => {
// Generate a HashMap with the Cipher UUID as key and the Folder UUID as value
cipher_folders = stream::iter(FolderCipher::find_by_user(user_uuid, conn).await).collect().await;
// Generate a HashSet of all the Cipher UUID's which are marked as favorite
cipher_favorites =
stream::iter(Favorite::get_all_cipher_uuid_by_user(user_uuid, conn).await).collect().await;
}
// Organization Sync does not support Folders and Favorits.
// If these are set, it will cause issues in the web-vault.
CipherSyncType::Organization => {}
}
// Generate a list of Cipher UUID's containing a Vec with one or more Attachment records
let mut cipher_attachments: HashMap<String, Vec<Attachment>> = HashMap::new();
for attachment in Attachment::find_all_by_ciphers(&cipher_uuids, conn).await {
cipher_attachments.entry(attachment.cipher_uuid.to_string()).or_default().push(attachment);
cipher_attachments.entry(attachment.cipher_uuid.clone()).or_default().push(attachment);
}
// Generate a HashMap with the Cipher UUID as key and the Folder UUID as value
let cipher_folders: HashMap<String, String> =
stream::iter(FolderCipher::find_by_user(user_uuid, conn).await).collect().await;
// Generate a HashSet of all the Cipher UUID's which are marked as favorite
let cipher_favorites: HashSet<String> =
stream::iter(Favorite::get_all_cipher_uuid_by_user(user_uuid, conn).await).collect().await;
// Generate a HashMap with the Cipher UUID as key and one or more Collection UUID's
let mut cipher_collections: HashMap<String, Vec<String>> = HashMap::new();
for (cipher, collection) in Cipher::get_collections_with_cipher_by_user(user_uuid, conn).await {
@@ -1514,14 +1530,14 @@ impl CipherSyncData {
// Generate a HashMap with the Organization UUID as key and the UserOrganization record
let user_organizations: HashMap<String, UserOrganization> =
stream::iter(UserOrganization::find_by_user(user_uuid, conn).await)
.map(|uo| (uo.org_uuid.to_string(), uo))
.map(|uo| (uo.org_uuid.clone(), uo))
.collect()
.await;
// Generate a HashMap with the User_Collections UUID as key and the CollectionUser record
let user_collections: HashMap<String, CollectionUser> =
stream::iter(CollectionUser::find_by_user(user_uuid, conn).await)
.map(|uc| (uc.collection_uuid.to_string(), uc))
.map(|uc| (uc.collection_uuid.clone(), uc))
.collect()
.await;

View File

@@ -5,7 +5,10 @@ use serde_json::Value;
use std::borrow::Borrow;
use crate::{
api::{core::CipherSyncData, EmptyResult, JsonResult, JsonUpcase, NumberOrString},
api::{
core::{CipherSyncData, CipherSyncType},
EmptyResult, JsonResult, JsonUpcase, NumberOrString,
},
auth::{decode_emergency_access_invite, Headers},
db::{models::*, DbConn, DbPool},
mail, CONFIG,
@@ -248,7 +251,8 @@ async fn send_invite(data: JsonUpcase<EmergencyAccessInviteData>, headers: Heade
Some(new_emergency_access.uuid),
Some(grantor_user.name.clone()),
Some(grantor_user.email),
)?;
)
.await?;
} else {
// Automatically mark user as accepted if no email invites
match User::find_by_mail(&email, &conn).await {
@@ -301,7 +305,8 @@ async fn resend_invite(emer_id: String, headers: Headers, conn: DbConn) -> Empty
Some(emergency_access.uuid),
Some(grantor_user.name.clone()),
Some(grantor_user.email),
)?;
)
.await?;
} else {
if Invitation::find_by_mail(&email, &conn).await.is_none() {
let invitation = Invitation::new(email);
@@ -363,7 +368,7 @@ async fn accept_invite(emer_id: String, data: JsonUpcase<AcceptData>, conn: DbCo
}
if CONFIG.mail_enabled() {
mail::send_emergency_access_invite_accepted(&grantor_user.email, &grantee_user.email)?;
mail::send_emergency_access_invite_accepted(&grantor_user.email, &grantee_user.email).await?;
}
Ok(())
@@ -446,7 +451,7 @@ async fn confirm_emergency_access(
emergency_access.save(&conn).await?;
if CONFIG.mail_enabled() {
mail::send_emergency_access_invite_confirmed(&grantee_user.email, &grantor_user.name)?;
mail::send_emergency_access_invite_confirmed(&grantee_user.email, &grantor_user.name).await?;
}
Ok(Json(emergency_access.to_json()))
} else {
@@ -492,7 +497,8 @@ async fn initiate_emergency_access(emer_id: String, headers: Headers, conn: DbCo
&initiating_user.name,
emergency_access.get_type_as_str(),
&emergency_access.wait_time_days.clone().to_string(),
)?;
)
.await?;
}
Ok(Json(emergency_access.to_json()))
}
@@ -528,7 +534,7 @@ async fn approve_emergency_access(emer_id: String, headers: Headers, conn: DbCon
emergency_access.save(&conn).await?;
if CONFIG.mail_enabled() {
mail::send_emergency_access_recovery_approved(&grantee_user.email, &grantor_user.name)?;
mail::send_emergency_access_recovery_approved(&grantee_user.email, &grantor_user.name).await?;
}
Ok(Json(emergency_access.to_json()))
} else {
@@ -568,7 +574,7 @@ async fn reject_emergency_access(emer_id: String, headers: Headers, conn: DbConn
emergency_access.save(&conn).await?;
if CONFIG.mail_enabled() {
mail::send_emergency_access_recovery_rejected(&grantee_user.email, &grantor_user.name)?;
mail::send_emergency_access_recovery_rejected(&grantee_user.email, &grantor_user.name).await?;
}
Ok(Json(emergency_access.to_json()))
} else {
@@ -596,7 +602,8 @@ async fn view_emergency_access(emer_id: String, headers: Headers, conn: DbConn)
}
let ciphers = Cipher::find_owned_by_user(&emergency_access.grantor_uuid, &conn).await;
let cipher_sync_data = CipherSyncData::new(&emergency_access.grantor_uuid, &ciphers, &conn).await;
let cipher_sync_data =
CipherSyncData::new(&emergency_access.grantor_uuid, &ciphers, CipherSyncType::User, &conn).await;
let ciphers_json = stream::iter(ciphers)
.then(|c| async {
@@ -754,7 +761,7 @@ pub async fn emergency_request_timeout_job(pool: DbPool) {
for mut emer in emergency_access_list {
if emer.recovery_initiated_at.is_some()
&& Utc::now().naive_utc()
>= emer.recovery_initiated_at.unwrap() + Duration::days(emer.wait_time_days as i64)
>= emer.recovery_initiated_at.unwrap() + Duration::days(i64::from(emer.wait_time_days))
{
emer.status = EmergencyAccessStatus::RecoveryApproved as i32;
emer.save(&conn).await.expect("Cannot save emergency access on job");
@@ -775,9 +782,11 @@ pub async fn emergency_request_timeout_job(pool: DbPool) {
&grantee_user.name.clone(),
emer.get_type_as_str(),
)
.await
.expect("Error on sending email");
mail::send_emergency_access_recovery_approved(&grantee_user.email, &grantor_user.name.clone())
.await
.expect("Error on sending email");
}
}
@@ -803,7 +812,7 @@ pub async fn emergency_notification_reminder_job(pool: DbPool) {
for mut emer in emergency_access_list {
if (emer.recovery_initiated_at.is_some()
&& Utc::now().naive_utc()
>= emer.recovery_initiated_at.unwrap() + Duration::days((emer.wait_time_days as i64) - 1))
>= emer.recovery_initiated_at.unwrap() + Duration::days((i64::from(emer.wait_time_days)) - 1))
&& (emer.last_notification_at.is_none()
|| (emer.last_notification_at.is_some()
&& Utc::now().naive_utc() >= emer.last_notification_at.unwrap() + Duration::days(1)))
@@ -827,6 +836,7 @@ pub async fn emergency_notification_reminder_job(pool: DbPool) {
emer.get_type_as_str(),
&emer.wait_time_days.to_string(), // TODO(jjlin): This should be the number of days left.
)
.await
.expect("Error on sending email");
}
}

View File

@@ -50,7 +50,7 @@ async fn post_folders(data: JsonUpcase<FolderData>, headers: Headers, conn: DbCo
let mut folder = Folder::new(headers.user.uuid, data.Name);
folder.save(&conn).await?;
nt.send_folder_update(UpdateType::FolderCreate, &folder);
nt.send_folder_update(UpdateType::FolderCreate, &folder).await;
Ok(Json(folder.to_json()))
}
@@ -88,7 +88,7 @@ async fn put_folder(
folder.name = data.Name;
folder.save(&conn).await?;
nt.send_folder_update(UpdateType::FolderUpdate, &folder);
nt.send_folder_update(UpdateType::FolderUpdate, &folder).await;
Ok(Json(folder.to_json()))
}
@@ -112,6 +112,6 @@ async fn delete_folder(uuid: String, headers: Headers, conn: DbConn, nt: Notify<
// Delete the actual folder entry
folder.delete(&conn).await?;
nt.send_folder_update(UpdateType::FolderDelete, &folder);
nt.send_folder_update(UpdateType::FolderDelete, &folder).await;
Ok(())
}

View File

@@ -7,7 +7,7 @@ mod sends;
pub mod two_factor;
pub use ciphers::purge_trashed_ciphers;
pub use ciphers::CipherSyncData;
pub use ciphers::{CipherSyncData, CipherSyncType};
pub use emergency_access::{emergency_notification_reminder_job, emergency_request_timeout_job};
pub use sends::purge_sends;
pub use two_factor::send_incomplete_2fa_notifications;

View File

@@ -5,8 +5,8 @@ use serde_json::Value;
use crate::{
api::{
core::CipherSyncData, EmptyResult, JsonResult, JsonUpcase, JsonUpcaseVec, Notify, NumberOrString, PasswordData,
UpdateType,
core::{CipherSyncData, CipherSyncType},
EmptyResult, JsonResult, JsonUpcase, JsonUpcaseVec, Notify, NumberOrString, PasswordData, UpdateType,
},
auth::{decode_invite, AdminHeaders, Headers, ManagerHeaders, ManagerHeadersLoose, OwnerHeaders},
db::{models::*, DbConn},
@@ -487,7 +487,7 @@ struct OrgIdData {
#[get("/ciphers/organization-details?<data..>")]
async fn get_org_details(data: OrgIdData, headers: Headers, conn: DbConn) -> Json<Value> {
let ciphers = Cipher::find_by_org(&data.organization_id, &conn).await;
let cipher_sync_data = CipherSyncData::new(&headers.user.uuid, &ciphers, &conn).await;
let cipher_sync_data = CipherSyncData::new(&headers.user.uuid, &ciphers, CipherSyncType::Organization, &conn).await;
let ciphers_json = stream::iter(ciphers)
.then(|c| async {
@@ -652,7 +652,8 @@ async fn send_invite(org_id: String, data: JsonUpcase<InviteData>, headers: Admi
Some(new_user.uuid),
&org_name,
Some(headers.user.email.clone()),
)?;
)
.await?;
}
}
@@ -732,7 +733,8 @@ async fn _reinvite_user(org_id: &str, user_org: &str, invited_by_email: &str, co
Some(user_org.uuid),
&org_name,
Some(invited_by_email.to_string()),
)?;
)
.await?;
} else {
let invitation = Invitation::new(user.email);
invitation.save(conn).await?;
@@ -830,10 +832,10 @@ async fn accept_invite(
};
if let Some(invited_by_email) = &claims.invited_by_email {
// User was invited to an organization, so they must be confirmed manually after acceptance
mail::send_invite_accepted(&claims.email, invited_by_email, &org_name)?;
mail::send_invite_accepted(&claims.email, invited_by_email, &org_name).await?;
} else {
// User was invited from /admin, so they are automatically confirmed
mail::send_invite_confirmed(&claims.email, &org_name)?;
mail::send_invite_confirmed(&claims.email, &org_name).await?;
}
}
@@ -928,7 +930,7 @@ async fn _confirm_invite(
Some(user) => user.email,
None => err!("Error looking up user."),
};
mail::send_invite_confirmed(&address, &org_name)?;
mail::send_invite_confirmed(&address, &org_name).await?;
}
user_to_confirm.save(conn).await
@@ -1298,7 +1300,7 @@ async fn put_policy(
let org = Organization::find_by_uuid(&member.org_uuid, &conn).await.unwrap();
let user = User::find_by_uuid(&member.user_uuid, &conn).await.unwrap();
mail::send_2fa_removed_from_org(&user.email, &org.name)?;
mail::send_2fa_removed_from_org(&user.email, &org.name).await?;
}
member.delete(&conn).await?;
}
@@ -1323,7 +1325,7 @@ async fn put_policy(
let org = Organization::find_by_uuid(&member.org_uuid, &conn).await.unwrap();
let user = User::find_by_uuid(&member.user_uuid, &conn).await.unwrap();
mail::send_single_org_removed_from_org(&user.email, &org.name)?;
mail::send_single_org_removed_from_org(&user.email, &org.name).await?;
}
member.delete(&conn).await?;
}
@@ -1462,7 +1464,8 @@ async fn import(org_id: String, data: JsonUpcase<OrgImportData>, headers: Header
Some(new_org_user.uuid),
&org_name,
Some(headers.user.email.clone()),
)?;
)
.await?;
}
}
}

View File

@@ -95,7 +95,7 @@ async fn enforce_disable_hide_email_policy(data: &SendData, headers: &Headers, c
Ok(())
}
async fn create_send(data: SendData, user_uuid: String) -> ApiResult<Send> {
fn create_send(data: SendData, user_uuid: String) -> ApiResult<Send> {
let data_val = if data.Type == SendType::Text as i32 {
data.Text
} else if data.Type == SendType::File as i32 {
@@ -117,7 +117,7 @@ async fn create_send(data: SendData, user_uuid: String) -> ApiResult<Send> {
);
}
let mut send = Send::new(data.Type, data.Name, data_str, data.Key, data.DeletionDate.naive_utc()).await;
let mut send = Send::new(data.Type, data.Name, data_str, data.Key, data.DeletionDate.naive_utc());
send.user_uuid = Some(user_uuid);
send.notes = data.Notes;
send.max_access_count = match data.MaxAccessCount {
@@ -171,9 +171,9 @@ async fn post_send(data: JsonUpcase<SendData>, headers: Headers, conn: DbConn, n
err!("File sends should use /api/sends/file")
}
let mut send = create_send(data, headers.user.uuid).await?;
let mut send = create_send(data, headers.user.uuid)?;
send.save(&conn).await?;
nt.send_send_update(UpdateType::SyncSendCreate, &send, &send.update_users_revision(&conn).await);
nt.send_send_update(UpdateType::SyncSendCreate, &send, &send.update_users_revision(&conn).await).await;
Ok(Json(send.to_json()))
}
@@ -211,7 +211,7 @@ async fn post_send_file(data: Form<UploadData<'_>>, headers: Headers, conn: DbCo
None => SIZE_525_MB,
};
let mut send = create_send(model, headers.user.uuid).await?;
let mut send = create_send(model, headers.user.uuid)?;
if send.atype != SendType::File as i32 {
err!("Send content is not a file");
}
@@ -225,7 +225,10 @@ async fn post_send_file(data: Form<UploadData<'_>>, headers: Headers, conn: DbCo
let folder_path = tokio::fs::canonicalize(&CONFIG.sends_folder()).await?.join(&send.uuid);
let file_path = folder_path.join(&file_id);
tokio::fs::create_dir_all(&folder_path).await?;
data.persist_to(&file_path).await?;
if let Err(_err) = data.persist_to(&file_path).await {
data.move_copy_to(file_path).await?
}
let mut data_value: Value = serde_json::from_str(&send.data)?;
if let Some(o) = data_value.as_object_mut() {
@@ -237,7 +240,7 @@ async fn post_send_file(data: Form<UploadData<'_>>, headers: Headers, conn: DbCo
// Save the changes in the database
send.save(&conn).await?;
nt.send_send_update(UpdateType::SyncSendUpdate, &send, &send.update_users_revision(&conn).await);
nt.send_send_update(UpdateType::SyncSendUpdate, &send, &send.update_users_revision(&conn).await).await;
Ok(Json(send.to_json()))
}
@@ -418,7 +421,7 @@ async fn put_send(
}
send.save(&conn).await?;
nt.send_send_update(UpdateType::SyncSendUpdate, &send, &send.update_users_revision(&conn).await);
nt.send_send_update(UpdateType::SyncSendUpdate, &send, &send.update_users_revision(&conn).await).await;
Ok(Json(send.to_json()))
}
@@ -435,7 +438,7 @@ async fn delete_send(id: String, headers: Headers, conn: DbConn, nt: Notify<'_>)
}
send.delete(&conn).await?;
nt.send_send_update(UpdateType::SyncSendDelete, &send, &send.update_users_revision(&conn).await);
nt.send_send_update(UpdateType::SyncSendDelete, &send, &send.update_users_revision(&conn).await).await;
Ok(())
}
@@ -455,7 +458,7 @@ async fn put_remove_password(id: String, headers: Headers, conn: DbConn, nt: Not
send.set_password(None);
send.save(&conn).await?;
nt.send_send_update(UpdateType::SyncSendUpdate, &send, &send.update_users_revision(&conn).await);
nt.send_send_update(UpdateType::SyncSendUpdate, &send, &send.update_users_revision(&conn).await).await;
Ok(Json(send.to_json()))
}

View File

@@ -139,7 +139,7 @@ pub async fn validate_totp_code(
// The amount of steps back and forward in time
// 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.
let steps = !CONFIG.authenticator_disable_time_drift() as i64;
let steps = i64::from(!CONFIG.authenticator_disable_time_drift());
// Get the current system time in UNIX Epoch (UTC)
let current_time = chrono::Utc::now();
@@ -154,7 +154,7 @@ pub async fn validate_totp_code(
let generated = totp_custom::<Sha1>(30, 6, &decoded_secret, time);
// Check the the given code equals the generated and if the time_step is larger then the one last used.
if generated == totp_code && time_step > twofactor.last_used as i64 {
if generated == totp_code && time_step > i64::from(twofactor.last_used) {
// If the step does not equals 0 the time is drifted either server or client side.
if step != 0 {
warn!("TOTP Time drift detected. The step offset is {}", step);
@@ -165,7 +165,7 @@ pub async fn validate_totp_code(
twofactor.last_used = time_step as i32;
twofactor.save(conn).await?;
return Ok(());
} else if generated == totp_code && time_step <= twofactor.last_used as i64 {
} else if generated == totp_code && time_step <= i64::from(twofactor.last_used) {
warn!("This TOTP or a TOTP code within {} steps back or forward has already been used!", steps);
err!(format!("Invalid TOTP code! Server time: {} IP: {}", current_time.format("%F %T UTC"), ip.ip));
}

View File

@@ -66,7 +66,7 @@ pub async fn send_token(user_uuid: &str, conn: &DbConn) -> EmptyResult {
twofactor.data = twofactor_data.to_json();
twofactor.save(conn).await?;
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")?).await?;
Ok(())
}
@@ -132,7 +132,7 @@ async fn send_email(data: JsonUpcase<SendEmailData>, headers: Headers, conn: DbC
let twofactor = TwoFactor::new(user.uuid, TwoFactorType::EmailVerificationChallenge, twofactor_data.to_json());
twofactor.save(&conn).await?;
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")?).await?;
Ok(())
}

View File

@@ -138,7 +138,7 @@ async fn disable_twofactor(data: JsonUpcase<DisableTwoFactorData>, headers: Head
if user_org.atype < UserOrgType::Admin {
if CONFIG.mail_enabled() {
let org = Organization::find_by_uuid(&user_org.org_uuid, &conn).await.unwrap();
mail::send_2fa_removed_from_org(&user.email, &org.name)?;
mail::send_2fa_removed_from_org(&user.email, &org.name).await?;
}
user_org.delete(&conn).await?;
}
@@ -183,6 +183,7 @@ pub async fn send_incomplete_2fa_notifications(pool: DbPool) {
user.email, login.ip_address
);
mail::send_incomplete_2fa_login(&user.email, &login.ip_address, &login.login_time, &login.device_name)
.await
.expect("Error sending incomplete 2FA email");
login.delete(&conn).await.expect("Error deleting incomplete 2FA record");
}

View File

@@ -147,7 +147,7 @@ async fn activate_yubikey(data: JsonUpcase<EnableYubikeyData>, headers: Headers,
verify_yubikey_otp(yubikey.to_owned()).map_res("Invalid Yubikey OTP provided")?;
}
let yubikey_ids: Vec<String> = yubikeys.into_iter().map(|x| (&x[..12]).to_owned()).collect();
let yubikey_ids: Vec<String> = yubikeys.into_iter().map(|x| (x[..12]).to_owned()).collect();
let yubikey_metadata = YubikeyMetadata {
Keys: yubikey_ids,

View File

@@ -1,5 +1,4 @@
use std::{
collections::HashMap,
net::IpAddr,
sync::Arc,
time::{Duration, SystemTime},
@@ -18,10 +17,9 @@ use tokio::{
fs::{create_dir_all, remove_file, symlink_metadata, File},
io::{AsyncReadExt, AsyncWriteExt},
net::lookup_host,
sync::RwLock,
};
use html5gum::{Emitter, EndTag, InfallibleTokenizer, Readable, StartTag, StringReader, Tokenizer};
use html5gum::{Emitter, EndTag, HtmlString, InfallibleTokenizer, Readable, StartTag, StringReader, Tokenizer};
use crate::{
error::Error,
@@ -53,7 +51,7 @@ static CLIENT: Lazy<Client> = Lazy::new(|| {
// Reuse the client between requests
let client = get_reqwest_client_builder()
.cookie_provider(cookie_store.clone())
.cookie_provider(Arc::clone(&cookie_store))
.timeout(Duration::from_secs(CONFIG.icon_download_timeout()))
.default_headers(default_headers.clone());
@@ -76,10 +74,10 @@ static CLIENT: Lazy<Client> = Lazy::new(|| {
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.
static ICON_BLACKLIST_REGEX: Lazy<RwLock<HashMap<String, Regex>>> = Lazy::new(|| RwLock::new(HashMap::new()));
static ICON_BLACKLIST_REGEX: Lazy<dashmap::DashMap<String, Regex>> = Lazy::new(dashmap::DashMap::new);
async fn icon_redirect(domain: &str, template: &str) -> Option<Redirect> {
if !is_valid_domain(domain).await {
if !is_valid_domain(domain) {
warn!("Invalid domain: {}", domain);
return None;
}
@@ -125,7 +123,7 @@ async fn icon_google(domain: String) -> Option<Redirect> {
async fn icon_internal(domain: String) -> Cached<(ContentType, Vec<u8>)> {
const FALLBACK_ICON: &[u8] = include_bytes!("../static/images/fallback-icon.png");
if !is_valid_domain(&domain).await {
if !is_valid_domain(&domain) {
warn!("Invalid domain: {}", domain);
return Cached::ttl(
(ContentType::new("image", "png"), FALLBACK_ICON.to_vec()),
@@ -146,7 +144,7 @@ async fn icon_internal(domain: String) -> Cached<(ContentType, Vec<u8>)> {
///
/// 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.
async 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.
@@ -280,6 +278,7 @@ mod tests {
use cached::proc_macro::cached;
#[cached(key = "String", convert = r#"{ domain.to_string() }"#, size = 16, time = 60)]
#[allow(clippy::unused_async)] // This is needed because cached causes a false-positive here.
async fn is_domain_blacklisted(domain: &str) -> bool {
if CONFIG.icon_blacklist_non_global_ips() {
if let Ok(s) = lookup_host((domain, 0)).await {
@@ -293,32 +292,25 @@ async fn is_domain_blacklisted(domain: &str) -> bool {
}
if let Some(blacklist) = CONFIG.icon_blacklist_regex() {
let mut regex_hashmap = ICON_BLACKLIST_REGEX.read().await;
// Use the pre-generate Regex stored in a Lazy HashMap if there's one, else generate it.
let regex = if let Some(regex) = regex_hashmap.get(&blacklist) {
regex
let is_match = if let Some(regex) = ICON_BLACKLIST_REGEX.get(&blacklist) {
regex.is_match(domain)
} else {
drop(regex_hashmap);
let mut regex_hashmap_write = ICON_BLACKLIST_REGEX.write().await;
// Clear the current list if the previous key doesn't exists.
// To prevent growing of the HashMap after someone has changed it via the admin interface.
if regex_hashmap_write.len() >= 1 {
regex_hashmap_write.clear();
if ICON_BLACKLIST_REGEX.len() >= 1 {
ICON_BLACKLIST_REGEX.clear();
}
// Generate the regex to store in too the Lazy Static HashMap.
let blacklist_regex = Regex::new(&blacklist);
regex_hashmap_write.insert(blacklist.to_string(), blacklist_regex.unwrap());
drop(regex_hashmap_write);
let blacklist_regex = Regex::new(&blacklist).unwrap();
let is_match = blacklist_regex.is_match(domain);
ICON_BLACKLIST_REGEX.insert(blacklist.clone(), blacklist_regex);
regex_hashmap = ICON_BLACKLIST_REGEX.read().await;
regex_hashmap.get(&blacklist).unwrap()
is_match
};
// Use the pre-generate Regex stored in a Lazy HashMap.
if regex.is_match(domain) {
if is_match {
debug!("Blacklisted domain: {} matched ICON_BLACKLIST_REGEX", domain);
return true;
}
@@ -335,7 +327,7 @@ async fn get_icon(domain: &str) -> Option<(Vec<u8>, String)> {
}
if let Some(icon) = get_cached_icon(&path).await {
let icon_type = match get_icon_type(&icon).await {
let icon_type = match get_icon_type(&icon) {
Some(x) => x,
_ => "x-icon",
};
@@ -425,7 +417,7 @@ impl Icon {
}
}
async fn get_favicons_node(
fn get_favicons_node(
dom: InfallibleTokenizer<StringReader<'_>, FaviconEmitter>,
icons: &mut Vec<Icon>,
url: &url::Url,
@@ -442,7 +434,7 @@ async fn get_favicons_node(
for token in dom {
match token {
FaviconToken::StartTag(tag) => {
if tag.name == TAG_LINK
if *tag.name == TAG_LINK
&& tag.attributes.contains_key(ATTR_REL)
&& tag.attributes.contains_key(ATTR_HREF)
{
@@ -452,7 +444,7 @@ async fn get_favicons_node(
if rel_value.contains("icon") && !rel_value.contains("mask-icon") {
icon_tags.push(tag);
}
} else if tag.name == TAG_BASE && tag.attributes.contains_key(ATTR_HREF) {
} else if *tag.name == TAG_BASE && tag.attributes.contains_key(ATTR_HREF) {
let href = std::str::from_utf8(tag.attributes.get(ATTR_HREF).unwrap()).unwrap_or_default();
debug!("Found base href: {href}");
base_url = match base_url.join(href) {
@@ -462,7 +454,7 @@ async fn get_favicons_node(
}
}
FaviconToken::EndTag(tag) => {
if tag.name == TAG_HEAD {
if *tag.name == TAG_HEAD {
break;
}
}
@@ -477,7 +469,7 @@ async fn get_favicons_node(
} else {
""
};
let priority = get_icon_priority(full_href.as_str(), sizes).await;
let priority = get_icon_priority(full_href.as_str(), sizes);
icons.push(Icon::new(priority, full_href.to_string()));
}
};
@@ -521,7 +513,7 @@ async fn get_icon_url(domain: &str) -> Result<IconUrlResult, Error> {
tld = domain_parts.next_back().unwrap(),
base = domain_parts.next_back().unwrap()
);
if is_valid_domain(&base_domain).await {
if is_valid_domain(&base_domain) {
let sslbase = format!("https://{base_domain}");
let httpbase = format!("http://{base_domain}");
debug!("[get_icon_url]: Trying without subdomains '{base_domain}'");
@@ -532,7 +524,7 @@ async fn get_icon_url(domain: &str) -> Result<IconUrlResult, Error> {
// When the domain is not an IP, and has less then 2 dots, try to add www. infront of it.
} else if is_ip.is_err() && domain.matches('.').count() < 2 {
let www_domain = format!("www.{domain}");
if is_valid_domain(&www_domain).await {
if is_valid_domain(&www_domain) {
let sslwww = format!("https://{www_domain}");
let httpwww = format!("http://{www_domain}");
debug!("[get_icon_url]: Trying with www. prefix '{www_domain}'");
@@ -564,7 +556,7 @@ async fn get_icon_url(domain: &str) -> Result<IconUrlResult, Error> {
let limited_reader = stream_to_bytes_limit(content, 384 * 1024).await?.to_vec();
let dom = Tokenizer::new_with_emitter(limited_reader.to_reader(), FaviconEmitter::default()).infallible();
get_favicons_node(dom, &mut iconlist, &url).await;
get_favicons_node(dom, &mut iconlist, &url);
} else {
// Add the default favicon.ico to the list with just the given domain
iconlist.push(Icon::new(35, format!("{ssldomain}/favicon.ico")));
@@ -612,12 +604,12 @@ async fn get_page_with_referer(url: &str, referer: &str) -> Result<Response, Err
///
/// # Example
/// ```
/// priority1 = get_icon_priority("http://example.com/path/to/a/favicon.png", "32x32").await;
/// priority2 = get_icon_priority("https://example.com/path/to/a/favicon.ico", "").await;
/// priority1 = get_icon_priority("http://example.com/path/to/a/favicon.png", "32x32");
/// priority2 = get_icon_priority("https://example.com/path/to/a/favicon.ico", "");
/// ```
async fn get_icon_priority(href: &str, sizes: &str) -> u8 {
fn get_icon_priority(href: &str, sizes: &str) -> u8 {
// Check if there is a dimension set
let (width, height) = parse_sizes(sizes).await;
let (width, height) = parse_sizes(sizes);
// Check if there is a size given
if width != 0 && height != 0 {
@@ -659,11 +651,11 @@ async fn get_icon_priority(href: &str, sizes: &str) -> u8 {
///
/// # Example
/// ```
/// let (width, height) = parse_sizes("64x64").await; // (64, 64)
/// let (width, height) = parse_sizes("x128x128").await; // (128, 128)
/// let (width, height) = parse_sizes("32").await; // (0, 0)
/// let (width, height) = parse_sizes("64x64"); // (64, 64)
/// let (width, height) = parse_sizes("x128x128"); // (128, 128)
/// let (width, height) = parse_sizes("32"); // (0, 0)
/// ```
async fn parse_sizes(sizes: &str) -> (u16, u16) {
fn parse_sizes(sizes: &str) -> (u16, u16) {
let mut width: u16 = 0;
let mut height: u16 = 0;
@@ -707,7 +699,7 @@ async fn download_icon(domain: &str) -> Result<(Bytes, Option<&str>), Error> {
// Also check if the size is atleast 67 bytes, which seems to be the smallest png i could create
if body.len() >= 67 {
// Check if the icon type is allowed, else try an icon from the list.
icon_type = get_icon_type(&body).await;
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;
@@ -725,7 +717,7 @@ async fn download_icon(domain: &str) -> Result<(Bytes, Option<&str>), Error> {
buffer = stream_to_bytes_limit(res, 5120 * 1024).await?; // 5120KB/5MB for each icon max (Same as icons.bitwarden.net)
// Check if the icon type is allowed, else try an icon from the list.
icon_type = get_icon_type(&buffer).await;
icon_type = get_icon_type(&buffer);
if icon_type.is_none() {
buffer.clear();
debug!("Icon from {}, is not a valid image type", icon.href);
@@ -760,7 +752,7 @@ async fn save_icon(path: &str, icon: &[u8]) {
}
}
async fn get_icon_type(bytes: &[u8]) -> Option<&'static str> {
fn get_icon_type(bytes: &[u8]) -> Option<&'static str> {
match bytes {
[137, 80, 78, 71, ..] => Some("png"),
[0, 0, 1, 0, ..] => Some("x-icon"),
@@ -839,17 +831,18 @@ impl reqwest::cookie::CookieStore for Jar {
/// Therefor parsing the HTML content is faster.
use std::collections::{BTreeSet, VecDeque};
#[derive(Debug)]
enum FaviconToken {
StartTag(StartTag),
EndTag(EndTag),
}
#[derive(Default)]
#[derive(Default, Debug)]
struct FaviconEmitter {
current_token: Option<FaviconToken>,
last_start_tag: Vec<u8>,
current_attribute: Option<(Vec<u8>, Vec<u8>)>,
seen_attributes: BTreeSet<Vec<u8>>,
last_start_tag: HtmlString,
current_attribute: Option<(HtmlString, HtmlString)>,
seen_attributes: BTreeSet<HtmlString>,
emitted_tokens: VecDeque<FaviconToken>,
}
@@ -896,18 +889,38 @@ impl Emitter for FaviconEmitter {
self.seen_attributes.clear();
}
fn emit_current_tag(&mut self) {
fn emit_current_tag(&mut self) -> Option<html5gum::State> {
self.flush_current_attribute();
let mut token = self.current_token.take().unwrap();
let mut emit = false;
match token {
FaviconToken::EndTag(_) => {
FaviconToken::EndTag(ref mut tag) => {
// Always clean seen attributes
self.seen_attributes.clear();
// Only trigger an emit for the </head> tag.
// This is matched, and will break the for-loop.
if *tag.name == b"head" {
emit = true;
}
}
FaviconToken::StartTag(ref mut tag) => {
self.set_last_start_tag(Some(&tag.name));
// Only trriger an emit for <link> and <base> tags.
// These are the only tags we want to parse.
if *tag.name == b"link" || *tag.name == b"base" {
self.set_last_start_tag(Some(&tag.name));
emit = true;
} else {
self.set_last_start_tag(None);
}
}
}
self.emit_token(token);
// Only emit the tags we want to parse.
if emit {
self.emit_token(token);
}
None
}
fn push_tag_name(&mut self, s: &[u8]) {
@@ -930,7 +943,7 @@ impl Emitter for FaviconEmitter {
fn init_attribute(&mut self) {
self.flush_current_attribute();
self.current_attribute = Some((Vec::new(), Vec::new()));
self.current_attribute = Some(Default::default());
}
fn push_attribute_name(&mut self, s: &[u8]) {

View File

@@ -135,7 +135,7 @@ async fn _password_login(data: ConnectData, conn: DbConn, ip: &ClientIp) -> Json
error!("Error updating user: {:#?}", e);
}
if let Err(e) = mail::send_verify_email(&user.email, &user.uuid) {
if let Err(e) = mail::send_verify_email(&user.email, &user.uuid).await {
error!("Error auto-sending email verification email: {:#?}", e);
}
}
@@ -150,7 +150,7 @@ async fn _password_login(data: ConnectData, conn: DbConn, ip: &ClientIp) -> Json
let twofactor_token = twofactor_auth(&user.uuid, &data, &mut device, ip, &conn).await?;
if CONFIG.mail_enabled() && new_device {
if let Err(e) = mail::send_new_device_logged_in(&user.email, &ip.ip.to_string(), &now, &device.name) {
if let Err(e) = mail::send_new_device_logged_in(&user.email, &ip.ip.to_string(), &now, &device.name).await {
error!("Error sending new device email: {:#?}", e);
if CONFIG.require_device_email() {
@@ -225,7 +225,7 @@ async fn _api_key_login(data: ConnectData, conn: DbConn, ip: &ClientIp) -> JsonR
if CONFIG.mail_enabled() && new_device {
let now = Utc::now().naive_utc();
if let Err(e) = mail::send_new_device_logged_in(&user.email, &ip.ip.to_string(), &now, &device.name) {
if let Err(e) = mail::send_new_device_logged_in(&user.email, &ip.ip.to_string(), &now, &device.name).await {
error!("Error sending new device email: {:#?}", e);
if CONFIG.require_device_email() {

View File

@@ -1,19 +1,41 @@
use std::sync::atomic::{AtomicBool, Ordering};
use std::{
net::SocketAddr,
sync::{
atomic::{AtomicBool, Ordering},
Arc,
},
time::Duration,
};
use rocket::serde::json::Json;
use rocket::Route;
use chrono::NaiveDateTime;
use futures::{SinkExt, StreamExt};
use rmpv::Value;
use rocket::{serde::json::Json, Route};
use serde_json::Value as JsonValue;
use tokio::{
net::{TcpListener, TcpStream},
sync::mpsc::Sender,
};
use tokio_tungstenite::{
accept_hdr_async,
tungstenite::{handshake, Message},
};
use crate::{api::EmptyResult, auth::Headers, Error, CONFIG};
use crate::{
api::EmptyResult,
auth::Headers,
db::models::{Cipher, Folder, Send, User},
Error, CONFIG,
};
pub fn routes() -> Vec<Route> {
routes![negotiate, websockets_err]
}
static SHOW_WEBSOCKETS_MSG: AtomicBool = AtomicBool::new(true);
#[get("/hub")]
fn websockets_err() -> EmptyResult {
static SHOW_WEBSOCKETS_MSG: AtomicBool = AtomicBool::new(true);
if CONFIG.websocket_enabled()
&& SHOW_WEBSOCKETS_MSG.compare_exchange(true, false, Ordering::Relaxed, Ordering::Relaxed).is_ok()
{
@@ -55,19 +77,6 @@ fn negotiate(_headers: Headers) -> Json<JsonValue> {
//
// Websockets server
//
use std::io;
use std::sync::Arc;
use std::thread;
use ws::{self, util::Token, Factory, Handler, Handshake, Message, Sender};
use chashmap::CHashMap;
use chrono::NaiveDateTime;
use serde_json::from_str;
use crate::db::models::{Cipher, Folder, Send, User};
use rmpv::Value;
fn serialize(val: Value) -> Vec<u8> {
use rmpv::encode::write_value;
@@ -118,192 +127,49 @@ fn convert_option<T: Into<Value>>(option: Option<T>) -> Value {
}
}
// Server WebSocket handler
pub struct WsHandler {
out: Sender,
user_uuid: Option<String>,
users: WebSocketUsers,
}
const RECORD_SEPARATOR: u8 = 0x1e;
const INITIAL_RESPONSE: [u8; 3] = [0x7b, 0x7d, RECORD_SEPARATOR]; // {, }, <RS>
#[derive(Deserialize)]
struct InitialMessage {
protocol: String,
#[derive(Deserialize, Copy, Clone, Eq, PartialEq)]
struct InitialMessage<'a> {
protocol: &'a str,
version: i32,
}
const PING_MS: u64 = 15_000;
const PING: Token = Token(1);
const ACCESS_TOKEN_KEY: &str = "access_token=";
impl WsHandler {
fn err(&self, msg: &'static str) -> ws::Result<()> {
self.out.close(ws::CloseCode::Invalid)?;
// We need to specifically return an IO error so ws closes the connection
let io_error = io::Error::from(io::ErrorKind::InvalidData);
Err(ws::Error::new(ws::ErrorKind::Io(io_error), msg))
}
fn get_request_token(&self, hs: Handshake) -> Option<String> {
use std::str::from_utf8;
// Verify we have a token header
if let Some(header_value) = hs.request.header("Authorization") {
if let Ok(converted) = from_utf8(header_value) {
if let Some(token_part) = converted.split("Bearer ").nth(1) {
return Some(token_part.into());
}
}
};
// Otherwise verify the query parameter value
let path = hs.request.resource();
if let Some(params) = path.split('?').nth(1) {
let params_iter = params.split('&').take(1);
for val in params_iter {
if let Some(stripped) = val.strip_prefix(ACCESS_TOKEN_KEY) {
return Some(stripped.into());
}
}
};
None
}
}
impl Handler for WsHandler {
fn on_open(&mut self, hs: Handshake) -> ws::Result<()> {
// Path == "/notifications/hub?id=<id>==&access_token=<access_token>"
//
// We don't use `id`, and as of around 2020-03-25, the official clients
// no longer seem to pass `id` (only `access_token`).
// Get user token from header or query parameter
let access_token = match self.get_request_token(hs) {
Some(token) => token,
_ => return self.err("Missing access token"),
};
// Validate the user
use crate::auth;
let claims = match auth::decode_login(access_token.as_str()) {
Ok(claims) => claims,
Err(_) => return self.err("Invalid access token provided"),
};
// Assign the user to the handler
let user_uuid = claims.sub;
self.user_uuid = Some(user_uuid.clone());
// Add the current Sender to the user list
let handler_insert = self.out.clone();
let handler_update = self.out.clone();
self.users.map.upsert(user_uuid, || vec![handler_insert], |ref mut v| v.push(handler_update));
// Schedule a ping to keep the connection alive
self.out.timeout(PING_MS, PING)
}
fn on_message(&mut self, msg: Message) -> ws::Result<()> {
if let Message::Text(text) = msg.clone() {
let json = &text[..text.len() - 1]; // Remove last char
if let Ok(InitialMessage {
protocol,
version,
}) = from_str::<InitialMessage>(json)
{
if &protocol == "messagepack" && version == 1 {
return self.out.send(&INITIAL_RESPONSE[..]); // Respond to initial message
}
}
}
// If it's not the initial message, just echo the message
self.out.send(msg)
}
fn on_timeout(&mut self, event: Token) -> ws::Result<()> {
if event == PING {
// send ping
self.out.send(create_ping())?;
// reschedule the timeout
self.out.timeout(PING_MS, PING)
} else {
Ok(())
}
}
}
struct WsFactory {
pub users: WebSocketUsers,
}
impl WsFactory {
pub fn init() -> Self {
WsFactory {
users: WebSocketUsers {
map: Arc::new(CHashMap::new()),
},
}
}
}
impl Factory for WsFactory {
type Handler = WsHandler;
fn connection_made(&mut self, out: Sender) -> Self::Handler {
WsHandler {
out,
user_uuid: None,
users: self.users.clone(),
}
}
fn connection_lost(&mut self, handler: Self::Handler) {
// Remove handler
if let Some(user_uuid) = &handler.user_uuid {
if let Some(mut user_conn) = self.users.map.get_mut(user_uuid) {
if let Some(pos) = user_conn.iter().position(|x| x == &handler.out) {
user_conn.remove(pos);
}
}
}
}
}
static INITIAL_MESSAGE: InitialMessage<'static> = InitialMessage {
protocol: "messagepack",
version: 1,
};
// We attach the UUID to the sender so we can differentiate them when we need to remove them from the Vec
type UserSenders = (uuid::Uuid, Sender<Message>);
#[derive(Clone)]
pub struct WebSocketUsers {
map: Arc<CHashMap<String, Vec<Sender>>>,
map: Arc<dashmap::DashMap<String, Vec<UserSenders>>>,
}
impl WebSocketUsers {
fn send_update(&self, user_uuid: &str, data: &[u8]) -> ws::Result<()> {
if let Some(user) = self.map.get(user_uuid) {
for sender in user.iter() {
sender.send(data)?;
async fn send_update(&self, user_uuid: &str, data: &[u8]) {
if let Some(user) = self.map.get(user_uuid).map(|v| v.clone()) {
for (_, sender) in user.iter() {
if sender.send(Message::binary(data)).await.is_err() {
// TODO: Delete from map here too?
}
}
}
Ok(())
}
// NOTE: The last modified date needs to be updated before calling these methods
pub fn send_user_update(&self, ut: UpdateType, user: &User) {
pub async fn send_user_update(&self, ut: UpdateType, user: &User) {
let data = create_update(
vec![("UserId".into(), user.uuid.clone().into()), ("Date".into(), serialize_date(user.updated_at))],
ut,
);
self.send_update(&user.uuid, &data).ok();
self.send_update(&user.uuid, &data).await;
}
pub fn send_folder_update(&self, ut: UpdateType, folder: &Folder) {
pub async fn send_folder_update(&self, ut: UpdateType, folder: &Folder) {
let data = create_update(
vec![
("Id".into(), folder.uuid.clone().into()),
@@ -313,10 +179,10 @@ impl WebSocketUsers {
ut,
);
self.send_update(&folder.user_uuid, &data).ok();
self.send_update(&folder.user_uuid, &data).await;
}
pub fn send_cipher_update(&self, ut: UpdateType, cipher: &Cipher, user_uuids: &[String]) {
pub async fn send_cipher_update(&self, ut: UpdateType, cipher: &Cipher, user_uuids: &[String]) {
let user_uuid = convert_option(cipher.user_uuid.clone());
let org_uuid = convert_option(cipher.organization_uuid.clone());
@@ -332,11 +198,11 @@ impl WebSocketUsers {
);
for uuid in user_uuids {
self.send_update(uuid, &data).ok();
self.send_update(uuid, &data).await;
}
}
pub fn send_send_update(&self, ut: UpdateType, send: &Send, user_uuids: &[String]) {
pub async fn send_send_update(&self, ut: UpdateType, send: &Send, user_uuids: &[String]) {
let user_uuid = convert_option(send.user_uuid.clone());
let data = create_update(
@@ -349,7 +215,7 @@ impl WebSocketUsers {
);
for uuid in user_uuids {
self.send_update(uuid, &data).ok();
self.send_update(uuid, &data).await;
}
}
}
@@ -392,7 +258,7 @@ fn create_ping() -> Vec<u8> {
}
#[allow(dead_code)]
#[derive(PartialEq)]
#[derive(Eq, PartialEq)]
pub enum UpdateType {
CipherUpdate = 0,
CipherCreate = 1,
@@ -416,27 +282,145 @@ pub enum UpdateType {
None = 100,
}
use rocket::State;
pub type Notify<'a> = &'a State<WebSocketUsers>;
pub type Notify<'a> = &'a rocket::State<WebSocketUsers>;
pub fn start_notification_server() -> WebSocketUsers {
let factory = WsFactory::init();
let users = factory.users.clone();
let users = WebSocketUsers {
map: Arc::new(dashmap::DashMap::new()),
};
if CONFIG.websocket_enabled() {
thread::spawn(move || {
let mut settings = ws::Settings::default();
settings.max_connections = 500;
settings.queue_size = 2;
settings.panic_on_internal = false;
let users2 = users.clone();
tokio::spawn(async move {
let addr = (CONFIG.websocket_address(), CONFIG.websocket_port());
info!("Starting WebSockets server on {}:{}", addr.0, addr.1);
let listener = TcpListener::bind(addr).await.expect("Can't listen on websocket port");
let ws = ws::Builder::new().with_settings(settings).build(factory).unwrap();
CONFIG.set_ws_shutdown_handle(ws.broadcaster());
ws.listen((CONFIG.websocket_address().as_str(), CONFIG.websocket_port())).unwrap();
let (shutdown_tx, mut shutdown_rx) = tokio::sync::oneshot::channel::<()>();
CONFIG.set_ws_shutdown_handle(shutdown_tx);
warn!("WS Server stopped!");
loop {
tokio::select! {
Ok((stream, addr)) = listener.accept() => {
tokio::spawn(handle_connection(stream, users2.clone(), addr));
}
_ = &mut shutdown_rx => {
break;
}
}
}
info!("Shutting down WebSockets server!")
});
}
users
}
async fn handle_connection(stream: TcpStream, users: WebSocketUsers, addr: SocketAddr) -> Result<(), Error> {
let mut user_uuid: Option<String> = None;
info!("Accepting WS connection from {addr}");
// Accept connection, do initial handshake, validate auth token and get the user ID
use handshake::server::{Request, Response};
let mut stream = accept_hdr_async(stream, |req: &Request, res: Response| {
if let Some(token) = get_request_token(req) {
if let Ok(claims) = crate::auth::decode_login(&token) {
user_uuid = Some(claims.sub);
return Ok(res);
}
}
Err(Response::builder().status(401).body(None).unwrap())
})
.await?;
let user_uuid = user_uuid.expect("User UUID should be set after the handshake");
// Add a channel to send messages to this client to the map
let entry_uuid = uuid::Uuid::new_v4();
let (tx, mut rx) = tokio::sync::mpsc::channel(100);
users.map.entry(user_uuid.clone()).or_default().push((entry_uuid, tx));
let mut interval = tokio::time::interval(Duration::from_secs(15));
loop {
tokio::select! {
res = stream.next() => {
match res {
Some(Ok(message)) => {
// Respond to any pings
if let Message::Ping(ping) = message {
if stream.send(Message::Pong(ping)).await.is_err() {
break;
}
continue;
} else if let Message::Pong(_) = message {
/* Ignored */
continue;
}
// We should receive an initial message with the protocol and version, and we will reply to it
if let Message::Text(ref message) = message {
let msg = message.strip_suffix(RECORD_SEPARATOR as char).unwrap_or(message);
if serde_json::from_str(msg).ok() == Some(INITIAL_MESSAGE) {
stream.send(Message::binary(INITIAL_RESPONSE)).await?;
continue;
}
}
// Just echo anything else the client sends
if stream.send(message).await.is_err() {
break;
}
}
_ => break,
}
}
res = rx.recv() => {
match res {
Some(res) => {
if stream.send(res).await.is_err() {
break;
}
},
None => break,
}
}
_= interval.tick() => {
if stream.send(Message::Ping(create_ping())).await.is_err() {
break;
}
}
}
}
info!("Closing WS connection from {addr}");
// Delete from map
users.map.entry(user_uuid).or_default().retain(|(uuid, _)| uuid != &entry_uuid);
Ok(())
}
fn get_request_token(req: &handshake::server::Request) -> Option<String> {
const ACCESS_TOKEN_KEY: &str = "access_token=";
if let Some(Ok(auth)) = req.headers().get("Authorization").map(|a| a.to_str()) {
if let Some(token_part) = auth.strip_prefix("Bearer ") {
return Some(token_part.to_owned());
}
}
if let Some(params) = req.uri().query() {
let params_iter = params.split('&').take(1);
for val in params_iter {
if let Some(stripped) = val.strip_prefix(ACCESS_TOKEN_KEY) {
return Some(stripped.to_owned());
}
}
}
None
}

View File

@@ -11,7 +11,6 @@ use serde::ser::Serialize;
use crate::{
error::{Error, MapResult},
util::read_file,
CONFIG,
};
@@ -30,13 +29,13 @@ static JWT_ADMIN_ISSUER: Lazy<String> = Lazy::new(|| format!("{}|admin", CONFIG.
static JWT_SEND_ISSUER: Lazy<String> = Lazy::new(|| format!("{}|send", CONFIG.domain_origin()));
static PRIVATE_RSA_KEY_VEC: Lazy<Vec<u8>> = Lazy::new(|| {
read_file(&CONFIG.private_rsa_key()).unwrap_or_else(|e| panic!("Error loading private RSA Key.\n{}", e))
std::fs::read(&CONFIG.private_rsa_key()).unwrap_or_else(|e| panic!("Error loading private RSA Key.\n{}", e))
});
static PRIVATE_RSA_KEY: Lazy<EncodingKey> = Lazy::new(|| {
EncodingKey::from_rsa_pem(&PRIVATE_RSA_KEY_VEC).unwrap_or_else(|e| panic!("Error decoding private RSA Key.\n{}", e))
});
static PUBLIC_RSA_KEY_VEC: Lazy<Vec<u8>> = Lazy::new(|| {
read_file(&CONFIG.public_rsa_key()).unwrap_or_else(|e| panic!("Error loading public RSA Key.\n{}", e))
std::fs::read(&CONFIG.public_rsa_key()).unwrap_or_else(|e| panic!("Error loading public RSA Key.\n{}", e))
});
static PUBLIC_RSA_KEY: Lazy<DecodingKey> = Lazy::new(|| {
DecodingKey::from_rsa_pem(&PUBLIC_RSA_KEY_VEC).unwrap_or_else(|e| panic!("Error decoding public RSA Key.\n{}", e))

View File

@@ -37,7 +37,7 @@ macro_rules! make_config {
struct Inner {
rocket_shutdown_handle: Option<rocket::Shutdown>,
ws_shutdown_handle: Option<ws::Sender>,
ws_shutdown_handle: Option<tokio::sync::oneshot::Sender<()>>,
templates: Handlebars<'static>,
config: ConfigItems,
@@ -91,8 +91,7 @@ macro_rules! make_config {
}
fn from_file(path: &str) -> Result<Self, Error> {
use crate::util::read_file_string;
let config_str = read_file_string(path)?;
let config_str = std::fs::read_to_string(path)?;
serde_json::from_str(&config_str).map_err(Into::into)
}
@@ -436,6 +435,8 @@ make_config! {
/// Password iterations |> Number of server-side passwords hashing iterations.
/// The changes only apply when a user changes their password. Not recommended to lower the value
password_iterations: i32, true, def, 100_000;
/// Allow password hints |> Controls whether users can set password hints. This setting applies globally to all users.
password_hints_allowed: bool, true, def, true;
/// Show password hint |> Controls whether a password hint should be shown directly in the web page
/// if SMTP service is not configured. Not recommended for publicly-accessible instances as this
/// provides unauthenticated access to potentially sensitive data.
@@ -948,19 +949,17 @@ impl Config {
self.inner.write().unwrap().rocket_shutdown_handle = Some(handle);
}
pub fn set_ws_shutdown_handle(&self, handle: ws::Sender) {
pub fn set_ws_shutdown_handle(&self, handle: tokio::sync::oneshot::Sender<()>) {
self.inner.write().unwrap().ws_shutdown_handle = Some(handle);
}
pub fn shutdown(&self) {
if let Ok(c) = self.inner.read() {
if let Some(handle) = c.ws_shutdown_handle.clone() {
handle.shutdown().ok();
if let Ok(mut c) = self.inner.write() {
if let Some(handle) = c.ws_shutdown_handle.take() {
handle.send(()).ok();
}
// Wait a bit before stopping the web server
tokio::runtime::Handle::current()
.block_on(async move { tokio::time::sleep(tokio::time::Duration::from_secs(1)).await });
if let Some(handle) = c.rocket_shutdown_handle.clone() {
if let Some(handle) = c.rocket_shutdown_handle.take() {
handle.notify();
}
}
@@ -1060,12 +1059,11 @@ fn js_escape_helper<'reg, 'rc>(
_rc: &mut RenderContext<'reg, 'rc>,
out: &mut dyn Output,
) -> HelperResult {
let param = h.param(0).ok_or_else(|| RenderError::new("Param not found for helper \"js_escape\""))?;
let param = h.param(0).ok_or_else(|| RenderError::new("Param not found for helper \"jsesc\""))?;
let no_quote = h.param(1).is_some();
let value =
param.value().as_str().ok_or_else(|| RenderError::new("Param for helper \"js_escape\" is not a String"))?;
let value = param.value().as_str().ok_or_else(|| RenderError::new("Param for helper \"jsesc\" is not a String"))?;
let mut escaped_value = value.replace('\\', "").replace('\'', "\\x22").replace('\"', "\\x27");
if !no_quote {

View File

@@ -87,11 +87,11 @@ impl Device {
nbf: time_now.timestamp(),
exp: (time_now + *DEFAULT_VALIDITY).timestamp(),
iss: JWT_LOGIN_ISSUER.to_string(),
sub: user.uuid.to_string(),
sub: user.uuid.clone(),
premium: true,
name: user.name.to_string(),
email: user.email.to_string(),
name: user.name.clone(),
email: user.email.clone(),
email_verified: !CONFIG.mail_enabled() || user.verified_at.is_some(),
orgowner,
@@ -99,8 +99,8 @@ impl Device {
orguser,
orgmanager,
sstamp: user.security_stamp.to_string(),
device: self.uuid.to_string(),
sstamp: user.security_stamp.clone(),
device: self.uuid.clone(),
scope,
amr: vec!["Application".into()],
};

View File

@@ -21,7 +21,7 @@ db_object! {
}
}
#[derive(Copy, Clone, PartialEq, num_derive::FromPrimitive)]
#[derive(Copy, Clone, Eq, PartialEq, num_derive::FromPrimitive)]
pub enum OrgPolicyType {
TwoFactorAuthentication = 0,
MasterPassword = 1,

View File

@@ -45,7 +45,7 @@ pub enum SendType {
}
impl Send {
pub async fn new(atype: i32, name: String, data: String, akey: String, deletion_date: NaiveDateTime) -> Self {
pub fn new(atype: i32, name: String, data: String, akey: String, deletion_date: NaiveDateTime) -> Self {
let now = Utc::now().naive_utc();
Self {

View File

@@ -171,7 +171,7 @@ impl User {
pub fn set_stamp_exception(&mut self, route_exception: Vec<String>) {
let stamp_exception = UserStampException {
routes: route_exception,
security_stamp: self.security_stamp.to_string(),
security_stamp: self.security_stamp.clone(),
expire: (Utc::now().naive_utc() + Duration::minutes(2)).timestamp(),
};
self.stamp_exception = Some(serde_json::to_string(&stamp_exception).unwrap_or_default());

View File

@@ -49,6 +49,7 @@ use rocket::error::Error as RocketErr;
use serde_json::{Error as SerdeErr, Value};
use std::io::Error as IoErr;
use std::time::SystemTimeError as TimeErr;
use tokio_tungstenite::tungstenite::Error as TungstError;
use webauthn_rs::error::WebauthnError as WebauthnErr;
use yubico::yubicoerror::YubicoError as YubiErr;
@@ -88,6 +89,7 @@ make_error! {
DieselCon(DieselConErr): _has_source, _api_error,
DieselMig(DieselMigErr): _has_source, _api_error,
Webauthn(WebauthnErr): _has_source, _api_error,
WebSocket(TungstError): _has_source, _api_error,
}
impl std::fmt::Debug for Error {

View File

@@ -4,11 +4,11 @@ use chrono::NaiveDateTime;
use percent_encoding::{percent_encode, NON_ALPHANUMERIC};
use lettre::{
message::{header, Mailbox, Message, MultiPart, SinglePart},
message::{Mailbox, Message, MultiPart},
transport::smtp::authentication::{Credentials, Mechanism as SmtpAuthMechanism},
transport::smtp::client::{Tls, TlsParameters},
transport::smtp::extension::ClientId,
Address, SmtpTransport, Transport,
Address, AsyncSmtpTransport, AsyncTransport, Tokio1Executor,
};
use crate::{
@@ -21,11 +21,11 @@ use crate::{
CONFIG,
};
fn mailer() -> SmtpTransport {
fn mailer() -> AsyncSmtpTransport<Tokio1Executor> {
use std::time::Duration;
let host = CONFIG.smtp_host().unwrap();
let smtp_client = SmtpTransport::builder_dangerous(host.as_str())
let smtp_client = AsyncSmtpTransport::<Tokio1Executor>::builder_dangerous(host.as_str())
.port(CONFIG.smtp_port())
.timeout(Some(Duration::from_secs(CONFIG.smtp_timeout())));
@@ -110,7 +110,7 @@ fn get_template(template_name: &str, data: &serde_json::Value) -> Result<(String
Ok((subject, body))
}
pub fn send_password_hint(address: &str, hint: Option<String>) -> EmptyResult {
pub async fn send_password_hint(address: &str, hint: Option<String>) -> EmptyResult {
let template_name = if hint.is_some() {
"email/pw_hint_some"
} else {
@@ -119,10 +119,10 @@ pub fn send_password_hint(address: &str, hint: Option<String>) -> EmptyResult {
let (subject, body_html, body_text) = get_text(template_name, json!({ "hint": hint, "url": CONFIG.domain() }))?;
send_email(address, &subject, body_html, body_text)
send_email(address, &subject, body_html, body_text).await
}
pub fn send_delete_account(address: &str, uuid: &str) -> EmptyResult {
pub async fn send_delete_account(address: &str, uuid: &str) -> EmptyResult {
let claims = generate_delete_claims(uuid.to_string());
let delete_token = encode_jwt(&claims);
@@ -136,10 +136,10 @@ pub fn send_delete_account(address: &str, uuid: &str) -> EmptyResult {
}),
)?;
send_email(address, &subject, body_html, body_text)
send_email(address, &subject, body_html, body_text).await
}
pub fn send_verify_email(address: &str, uuid: &str) -> EmptyResult {
pub async fn send_verify_email(address: &str, uuid: &str) -> EmptyResult {
let claims = generate_verify_email_claims(uuid.to_string());
let verify_email_token = encode_jwt(&claims);
@@ -153,10 +153,10 @@ pub fn send_verify_email(address: &str, uuid: &str) -> EmptyResult {
}),
)?;
send_email(address, &subject, body_html, body_text)
send_email(address, &subject, body_html, body_text).await
}
pub fn send_welcome(address: &str) -> EmptyResult {
pub async fn send_welcome(address: &str) -> EmptyResult {
let (subject, body_html, body_text) = get_text(
"email/welcome",
json!({
@@ -164,10 +164,10 @@ pub fn send_welcome(address: &str) -> EmptyResult {
}),
)?;
send_email(address, &subject, body_html, body_text)
send_email(address, &subject, body_html, body_text).await
}
pub fn send_welcome_must_verify(address: &str, uuid: &str) -> EmptyResult {
pub async fn send_welcome_must_verify(address: &str, uuid: &str) -> EmptyResult {
let claims = generate_verify_email_claims(uuid.to_string());
let verify_email_token = encode_jwt(&claims);
@@ -180,10 +180,10 @@ pub fn send_welcome_must_verify(address: &str, uuid: &str) -> EmptyResult {
}),
)?;
send_email(address, &subject, body_html, body_text)
send_email(address, &subject, body_html, body_text).await
}
pub fn send_2fa_removed_from_org(address: &str, org_name: &str) -> EmptyResult {
pub async fn send_2fa_removed_from_org(address: &str, org_name: &str) -> EmptyResult {
let (subject, body_html, body_text) = get_text(
"email/send_2fa_removed_from_org",
json!({
@@ -192,10 +192,10 @@ pub fn send_2fa_removed_from_org(address: &str, org_name: &str) -> EmptyResult {
}),
)?;
send_email(address, &subject, body_html, body_text)
send_email(address, &subject, body_html, body_text).await
}
pub fn send_single_org_removed_from_org(address: &str, org_name: &str) -> EmptyResult {
pub async fn send_single_org_removed_from_org(address: &str, org_name: &str) -> EmptyResult {
let (subject, body_html, body_text) = get_text(
"email/send_single_org_removed_from_org",
json!({
@@ -204,10 +204,10 @@ pub fn send_single_org_removed_from_org(address: &str, org_name: &str) -> EmptyR
}),
)?;
send_email(address, &subject, body_html, body_text)
send_email(address, &subject, body_html, body_text).await
}
pub fn send_invite(
pub async fn send_invite(
address: &str,
uuid: &str,
org_id: Option<String>,
@@ -236,10 +236,10 @@ pub fn send_invite(
}),
)?;
send_email(address, &subject, body_html, body_text)
send_email(address, &subject, body_html, body_text).await
}
pub fn send_emergency_access_invite(
pub async fn send_emergency_access_invite(
address: &str,
uuid: &str,
emer_id: Option<String>,
@@ -267,10 +267,10 @@ pub fn send_emergency_access_invite(
}),
)?;
send_email(address, &subject, body_html, body_text)
send_email(address, &subject, body_html, body_text).await
}
pub fn send_emergency_access_invite_accepted(address: &str, grantee_email: &str) -> EmptyResult {
pub async fn send_emergency_access_invite_accepted(address: &str, grantee_email: &str) -> EmptyResult {
let (subject, body_html, body_text) = get_text(
"email/emergency_access_invite_accepted",
json!({
@@ -279,10 +279,10 @@ pub fn send_emergency_access_invite_accepted(address: &str, grantee_email: &str)
}),
)?;
send_email(address, &subject, body_html, body_text)
send_email(address, &subject, body_html, body_text).await
}
pub fn send_emergency_access_invite_confirmed(address: &str, grantor_name: &str) -> EmptyResult {
pub async fn send_emergency_access_invite_confirmed(address: &str, grantor_name: &str) -> EmptyResult {
let (subject, body_html, body_text) = get_text(
"email/emergency_access_invite_confirmed",
json!({
@@ -291,10 +291,10 @@ pub fn send_emergency_access_invite_confirmed(address: &str, grantor_name: &str)
}),
)?;
send_email(address, &subject, body_html, body_text)
send_email(address, &subject, body_html, body_text).await
}
pub fn send_emergency_access_recovery_approved(address: &str, grantor_name: &str) -> EmptyResult {
pub async fn send_emergency_access_recovery_approved(address: &str, grantor_name: &str) -> EmptyResult {
let (subject, body_html, body_text) = get_text(
"email/emergency_access_recovery_approved",
json!({
@@ -303,10 +303,10 @@ pub fn send_emergency_access_recovery_approved(address: &str, grantor_name: &str
}),
)?;
send_email(address, &subject, body_html, body_text)
send_email(address, &subject, body_html, body_text).await
}
pub fn send_emergency_access_recovery_initiated(
pub async fn send_emergency_access_recovery_initiated(
address: &str,
grantee_name: &str,
atype: &str,
@@ -322,10 +322,10 @@ pub fn send_emergency_access_recovery_initiated(
}),
)?;
send_email(address, &subject, body_html, body_text)
send_email(address, &subject, body_html, body_text).await
}
pub fn send_emergency_access_recovery_reminder(
pub async fn send_emergency_access_recovery_reminder(
address: &str,
grantee_name: &str,
atype: &str,
@@ -341,10 +341,10 @@ pub fn send_emergency_access_recovery_reminder(
}),
)?;
send_email(address, &subject, body_html, body_text)
send_email(address, &subject, body_html, body_text).await
}
pub fn send_emergency_access_recovery_rejected(address: &str, grantor_name: &str) -> EmptyResult {
pub async fn send_emergency_access_recovery_rejected(address: &str, grantor_name: &str) -> EmptyResult {
let (subject, body_html, body_text) = get_text(
"email/emergency_access_recovery_rejected",
json!({
@@ -353,10 +353,10 @@ pub fn send_emergency_access_recovery_rejected(address: &str, grantor_name: &str
}),
)?;
send_email(address, &subject, body_html, body_text)
send_email(address, &subject, body_html, body_text).await
}
pub fn send_emergency_access_recovery_timed_out(address: &str, grantee_name: &str, atype: &str) -> EmptyResult {
pub async fn send_emergency_access_recovery_timed_out(address: &str, grantee_name: &str, atype: &str) -> EmptyResult {
let (subject, body_html, body_text) = get_text(
"email/emergency_access_recovery_timed_out",
json!({
@@ -366,10 +366,10 @@ pub fn send_emergency_access_recovery_timed_out(address: &str, grantee_name: &st
}),
)?;
send_email(address, &subject, body_html, body_text)
send_email(address, &subject, body_html, body_text).await
}
pub fn send_invite_accepted(new_user_email: &str, address: &str, org_name: &str) -> EmptyResult {
pub async fn send_invite_accepted(new_user_email: &str, address: &str, org_name: &str) -> EmptyResult {
let (subject, body_html, body_text) = get_text(
"email/invite_accepted",
json!({
@@ -379,10 +379,10 @@ pub fn send_invite_accepted(new_user_email: &str, address: &str, org_name: &str)
}),
)?;
send_email(address, &subject, body_html, body_text)
send_email(address, &subject, body_html, body_text).await
}
pub fn send_invite_confirmed(address: &str, org_name: &str) -> EmptyResult {
pub async fn send_invite_confirmed(address: &str, org_name: &str) -> EmptyResult {
let (subject, body_html, body_text) = get_text(
"email/invite_confirmed",
json!({
@@ -391,10 +391,10 @@ pub fn send_invite_confirmed(address: &str, org_name: &str) -> EmptyResult {
}),
)?;
send_email(address, &subject, body_html, body_text)
send_email(address, &subject, body_html, body_text).await
}
pub fn send_new_device_logged_in(address: &str, ip: &str, dt: &NaiveDateTime, device: &str) -> EmptyResult {
pub async fn send_new_device_logged_in(address: &str, ip: &str, dt: &NaiveDateTime, device: &str) -> EmptyResult {
use crate::util::upcase_first;
let device = upcase_first(device);
@@ -409,10 +409,10 @@ pub fn send_new_device_logged_in(address: &str, ip: &str, dt: &NaiveDateTime, de
}),
)?;
send_email(address, &subject, body_html, body_text)
send_email(address, &subject, body_html, body_text).await
}
pub fn send_incomplete_2fa_login(address: &str, ip: &str, dt: &NaiveDateTime, device: &str) -> EmptyResult {
pub async fn send_incomplete_2fa_login(address: &str, ip: &str, dt: &NaiveDateTime, device: &str) -> EmptyResult {
use crate::util::upcase_first;
let device = upcase_first(device);
@@ -428,10 +428,10 @@ pub fn send_incomplete_2fa_login(address: &str, ip: &str, dt: &NaiveDateTime, de
}),
)?;
send_email(address, &subject, body_html, body_text)
send_email(address, &subject, body_html, body_text).await
}
pub fn send_token(address: &str, token: &str) -> EmptyResult {
pub async fn send_token(address: &str, token: &str) -> EmptyResult {
let (subject, body_html, body_text) = get_text(
"email/twofactor_email",
json!({
@@ -440,10 +440,10 @@ pub fn send_token(address: &str, token: &str) -> EmptyResult {
}),
)?;
send_email(address, &subject, body_html, body_text)
send_email(address, &subject, body_html, body_text).await
}
pub fn send_change_email(address: &str, token: &str) -> EmptyResult {
pub async fn send_change_email(address: &str, token: &str) -> EmptyResult {
let (subject, body_html, body_text) = get_text(
"email/change_email",
json!({
@@ -452,10 +452,10 @@ pub fn send_change_email(address: &str, token: &str) -> EmptyResult {
}),
)?;
send_email(address, &subject, body_html, body_text)
send_email(address, &subject, body_html, body_text).await
}
pub fn send_test(address: &str) -> EmptyResult {
pub async fn send_test(address: &str) -> EmptyResult {
let (subject, body_html, body_text) = get_text(
"email/smtp_test",
json!({
@@ -463,43 +463,19 @@ pub fn send_test(address: &str) -> EmptyResult {
}),
)?;
send_email(address, &subject, body_html, body_text)
send_email(address, &subject, body_html, body_text).await
}
fn send_email(address: &str, subject: &str, body_html: String, body_text: String) -> EmptyResult {
let address_split: Vec<&str> = address.rsplitn(2, '@').collect();
if address_split.len() != 2 {
err!("Invalid email address (no @)");
}
let domain_puny = match idna::domain_to_ascii_strict(address_split[0]) {
Ok(d) => d,
Err(_) => err!("Can't convert email domain to ASCII representation"),
};
let address = format!("{}@{}", address_split[1], domain_puny);
let html = SinglePart::builder()
// We force Base64 encoding because in the past we had issues with different encodings.
.header(header::ContentTransferEncoding::Base64)
.header(header::ContentType::TEXT_HTML)
.body(body_html);
let text = SinglePart::builder()
// We force Base64 encoding because in the past we had issues with different encodings.
.header(header::ContentTransferEncoding::Base64)
.header(header::ContentType::TEXT_PLAIN)
.body(body_text);
async fn send_email(address: &str, subject: &str, body_html: String, body_text: String) -> EmptyResult {
let smtp_from = &CONFIG.smtp_from();
let email = Message::builder()
.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(Some(CONFIG.smtp_from_name()), Address::from_str(smtp_from)?))
.subject(subject)
.multipart(MultiPart::alternative().singlepart(text).singlepart(html))?;
.multipart(MultiPart::alternative_plain_html(body_text, body_html))?;
match mailer().send(&email) {
match mailer().send(email).await {
Ok(_) => Ok(()),
// Match some common errors and make them more user friendly
Err(e) => {

View File

@@ -1,6 +1,30 @@
#![forbid(unsafe_code)]
#![warn(rust_2018_idioms)]
#![warn(rust_2021_compatibility)]
#![forbid(unsafe_code, non_ascii_idents)]
#![deny(
rust_2018_idioms,
rust_2021_compatibility,
noop_method_call,
pointer_structural_match,
trivial_casts,
trivial_numeric_casts,
unused_import_braces,
clippy::cast_lossless,
clippy::clone_on_ref_ptr,
clippy::equatable_if_let,
clippy::float_cmp_const,
clippy::inefficient_to_string,
clippy::linkedlist,
clippy::macro_use_imports,
clippy::manual_assert,
clippy::match_wildcard_for_single_variants,
clippy::mem_forget,
clippy::string_add_assign,
clippy::string_to_string,
clippy::unnecessary_join,
clippy::unnecessary_self_imports,
clippy::unused_async,
clippy::verbose_file_reads,
clippy::zero_sized_map_values
)]
#![cfg_attr(feature = "unstable", feature(ip))]
// The recursion_limit is mainly triggered by the json!() macro.
// The more key/value pairs there are the more recursion occurs.
@@ -145,15 +169,13 @@ fn init_logging(level: log::LevelFilter) -> Result<(), fern::InitError> {
// Hide failed to close stream messages
.level_for("hyper::server", log::LevelFilter::Warn)
// Silence rocket logs
.level_for("_", log::LevelFilter::Off)
.level_for("_", log::LevelFilter::Warn)
.level_for("rocket::launch", log::LevelFilter::Error)
.level_for("rocket::launch_", log::LevelFilter::Error)
.level_for("rocket::rocket", log::LevelFilter::Warn)
.level_for("rocket::server", log::LevelFilter::Warn)
.level_for("rocket::fairing::fairings", log::LevelFilter::Warn)
.level_for("rocket::shield::shield", log::LevelFilter::Warn)
// Never show html5ever and hyper::proto logs, too noisy
.level_for("html5ever", log::LevelFilter::Off)
.level_for("hyper::proto", log::LevelFilter::Off)
.level_for("hyper::client", log::LevelFilter::Off)
// Prevent cookie_store logs
@@ -276,6 +298,20 @@ fn check_data_folder() {
}
exit(1);
}
let persistent_volume_check_file = format!("{data_folder}/vaultwarden_docker_persistent_volume_check");
let check_file = Path::new(&persistent_volume_check_file);
if check_file.exists() && std::env::var("I_REALLY_WANT_VOLATILE_STORAGE").is_err() {
error!(
"No persistent volume!\n\
########################################################################################\n\
# It looks like you did not configure a persistent volume! #\n\
# This will result in permanent data loss when the container is removed or updated! #\n\
# If you really want to use volatile storage set `I_REALLY_WANT_VOLATILE_STORAGE=true` #\n\
########################################################################################\n"
);
exit(1);
}
}
fn check_rsa_keys() -> Result<(), crate::error::Error> {
@@ -292,7 +328,7 @@ fn check_rsa_keys() -> Result<(), crate::error::Error> {
}
if !util::file_exists(&pub_path) {
let rsa_key = openssl::rsa::Rsa::private_key_from_pem(&util::read_file(&priv_path)?)?;
let rsa_key = openssl::rsa::Rsa::private_key_from_pem(&std::fs::read(&priv_path)?)?;
let pub_key = rsa_key.public_key_to_pem()?;
crate::util::write_file(&pub_path, &pub_key)?;
@@ -383,7 +419,7 @@ async fn schedule_jobs(pool: db::DbPool) {
thread::Builder::new()
.name("job-scheduler".to_string())
.spawn(move || {
use job_scheduler::{Job, JobScheduler};
use job_scheduler_ng::{Job, JobScheduler};
let _runtime_guard = runtime.enter();
let mut sched = JobScheduler::new();

View File

@@ -328,6 +328,7 @@
"Type": 33,
"Domains": [
"healthcare.gov",
"cuidadodesalud.gov",
"cms.gov"
],
"Excluded": false
@@ -902,6 +903,7 @@
{
"Type": 85,
"Domains": [
"proton.me",
"protonmail.com",
"protonvpn.com"
],

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -4,13 +4,175 @@
*
* To rebuild or modify this file with the latest versions of the included
* software please visit:
* https://datatables.net/download/#bs5/dt-1.11.5
* https://datatables.net/download/#bs5/dt-1.12.1
*
* Included libraries:
* DataTables 1.11.5
* DataTables 1.12.1
*/
@charset "UTF-8";
table.dataTable td.dt-control {
text-align: center;
cursor: pointer;
}
table.dataTable td.dt-control:before {
height: 1em;
width: 1em;
margin-top: -9px;
display: inline-block;
color: white;
border: 0.15em solid white;
border-radius: 1em;
box-shadow: 0 0 0.2em #444;
box-sizing: content-box;
text-align: center;
text-indent: 0 !important;
font-family: "Courier New", Courier, monospace;
line-height: 1em;
content: "+";
background-color: #31b131;
}
table.dataTable tr.dt-hasChild td.dt-control:before {
content: "-";
background-color: #d33333;
}
table.dataTable thead > tr > th.sorting, table.dataTable thead > tr > th.sorting_asc, table.dataTable thead > tr > th.sorting_desc, table.dataTable thead > tr > th.sorting_asc_disabled, table.dataTable thead > tr > th.sorting_desc_disabled,
table.dataTable thead > tr > td.sorting,
table.dataTable thead > tr > td.sorting_asc,
table.dataTable thead > tr > td.sorting_desc,
table.dataTable thead > tr > td.sorting_asc_disabled,
table.dataTable thead > tr > td.sorting_desc_disabled {
cursor: pointer;
position: relative;
padding-right: 26px;
}
table.dataTable thead > tr > th.sorting:before, table.dataTable thead > tr > th.sorting:after, table.dataTable thead > tr > th.sorting_asc:before, table.dataTable thead > tr > th.sorting_asc:after, table.dataTable thead > tr > th.sorting_desc:before, table.dataTable thead > tr > th.sorting_desc:after, table.dataTable thead > tr > th.sorting_asc_disabled:before, table.dataTable thead > tr > th.sorting_asc_disabled:after, table.dataTable thead > tr > th.sorting_desc_disabled:before, table.dataTable thead > tr > th.sorting_desc_disabled:after,
table.dataTable thead > tr > td.sorting:before,
table.dataTable thead > tr > td.sorting:after,
table.dataTable thead > tr > td.sorting_asc:before,
table.dataTable thead > tr > td.sorting_asc:after,
table.dataTable thead > tr > td.sorting_desc:before,
table.dataTable thead > tr > td.sorting_desc:after,
table.dataTable thead > tr > td.sorting_asc_disabled:before,
table.dataTable thead > tr > td.sorting_asc_disabled:after,
table.dataTable thead > tr > td.sorting_desc_disabled:before,
table.dataTable thead > tr > td.sorting_desc_disabled:after {
position: absolute;
display: block;
opacity: 0.125;
right: 10px;
line-height: 9px;
font-size: 0.9em;
}
table.dataTable thead > tr > th.sorting:before, table.dataTable thead > tr > th.sorting_asc:before, table.dataTable thead > tr > th.sorting_desc:before, table.dataTable thead > tr > th.sorting_asc_disabled:before, table.dataTable thead > tr > th.sorting_desc_disabled:before,
table.dataTable thead > tr > td.sorting:before,
table.dataTable thead > tr > td.sorting_asc:before,
table.dataTable thead > tr > td.sorting_desc:before,
table.dataTable thead > tr > td.sorting_asc_disabled:before,
table.dataTable thead > tr > td.sorting_desc_disabled:before {
bottom: 50%;
content: "▴";
}
table.dataTable thead > tr > th.sorting:after, table.dataTable thead > tr > th.sorting_asc:after, table.dataTable thead > tr > th.sorting_desc:after, table.dataTable thead > tr > th.sorting_asc_disabled:after, table.dataTable thead > tr > th.sorting_desc_disabled:after,
table.dataTable thead > tr > td.sorting:after,
table.dataTable thead > tr > td.sorting_asc:after,
table.dataTable thead > tr > td.sorting_desc:after,
table.dataTable thead > tr > td.sorting_asc_disabled:after,
table.dataTable thead > tr > td.sorting_desc_disabled:after {
top: 50%;
content: "▾";
}
table.dataTable thead > tr > th.sorting_asc:before, table.dataTable thead > tr > th.sorting_desc:after,
table.dataTable thead > tr > td.sorting_asc:before,
table.dataTable thead > tr > td.sorting_desc:after {
opacity: 0.6;
}
table.dataTable thead > tr > th.sorting_desc_disabled:after, table.dataTable thead > tr > th.sorting_asc_disabled:before,
table.dataTable thead > tr > td.sorting_desc_disabled:after,
table.dataTable thead > tr > td.sorting_asc_disabled:before {
display: none;
}
table.dataTable thead > tr > th:active,
table.dataTable thead > tr > td:active {
outline: none;
}
div.dataTables_scrollBody table.dataTable thead > tr > th:before, div.dataTables_scrollBody table.dataTable thead > tr > th:after,
div.dataTables_scrollBody table.dataTable thead > tr > td:before,
div.dataTables_scrollBody table.dataTable thead > tr > td:after {
display: none;
}
div.dataTables_processing {
position: absolute;
top: 50%;
left: 50%;
width: 200px;
margin-left: -100px;
margin-top: -26px;
text-align: center;
padding: 2px;
}
div.dataTables_processing > div:last-child {
position: relative;
width: 80px;
height: 15px;
margin: 1em auto;
}
div.dataTables_processing > div:last-child > div {
position: absolute;
top: 0;
width: 13px;
height: 13px;
border-radius: 50%;
background: rgba(13, 110, 253, 0.9);
animation-timing-function: cubic-bezier(0, 1, 1, 0);
}
div.dataTables_processing > div:last-child > div:nth-child(1) {
left: 8px;
animation: datatables-loader-1 0.6s infinite;
}
div.dataTables_processing > div:last-child > div:nth-child(2) {
left: 8px;
animation: datatables-loader-2 0.6s infinite;
}
div.dataTables_processing > div:last-child > div:nth-child(3) {
left: 32px;
animation: datatables-loader-2 0.6s infinite;
}
div.dataTables_processing > div:last-child > div:nth-child(4) {
left: 56px;
animation: datatables-loader-3 0.6s infinite;
}
@keyframes datatables-loader-1 {
0% {
transform: scale(0);
}
100% {
transform: scale(1);
}
}
@keyframes datatables-loader-3 {
0% {
transform: scale(1);
}
100% {
transform: scale(0);
}
}
@keyframes datatables-loader-2 {
0% {
transform: translate(0, 0);
}
100% {
transform: translate(24px, 0);
}
}
table.dataTable.nowrap th, table.dataTable.nowrap td {
white-space: nowrap;
}
table.dataTable th.dt-left,
table.dataTable td.dt-left {
text-align: left;
@@ -32,6 +194,12 @@ table.dataTable th.dt-nowrap,
table.dataTable td.dt-nowrap {
white-space: nowrap;
}
table.dataTable thead th,
table.dataTable thead td,
table.dataTable tfoot th,
table.dataTable tfoot td {
text-align: left;
}
table.dataTable thead th.dt-head-left,
table.dataTable thead td.dt-head-left,
table.dataTable tfoot th.dt-head-left,
@@ -82,31 +250,6 @@ table.dataTable tbody th.dt-body-nowrap,
table.dataTable tbody td.dt-body-nowrap {
white-space: nowrap;
}
table.dataTable td.dt-control {
text-align: center;
cursor: pointer;
}
table.dataTable td.dt-control:before {
height: 1em;
width: 1em;
margin-top: -9px;
display: inline-block;
color: white;
border: 0.15em solid white;
border-radius: 1em;
box-shadow: 0 0 0.2em #444;
box-sizing: content-box;
text-align: center;
text-indent: 0 !important;
font-family: "Courier New", Courier, monospace;
line-height: 1em;
content: "+";
background-color: #31b131;
}
table.dataTable tr.dt-hasChild td.dt-control:before {
content: "-";
background-color: #d33333;
}
/*! Bootstrap 5 integration for DataTables
*
@@ -134,6 +277,28 @@ table.dataTable.nowrap th,
table.dataTable.nowrap td {
white-space: nowrap;
}
table.dataTable.table-striped > tbody > tr:nth-of-type(2n+1) > * {
box-shadow: none;
}
table.dataTable > tbody > tr {
background-color: transparent;
}
table.dataTable > tbody > tr.selected > * {
box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.9);
color: white;
}
table.dataTable.table-striped > tbody > tr.odd > * {
box-shadow: inset 0 0 0 9999px rgba(0, 0, 0, 0.05);
}
table.dataTable.table-striped > tbody > tr.odd.selected > * {
box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.95);
}
table.dataTable.table-hover > tbody > tr:hover > * {
box-shadow: inset 0 0 0 9999px rgba(0, 0, 0, 0.075);
}
table.dataTable.table-hover > tbody > tr.selected:hover > * {
box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.975);
}
div.dataTables_wrapper div.dataTables_length label {
font-weight: normal;
@@ -170,71 +335,6 @@ div.dataTables_wrapper div.dataTables_paginate ul.pagination {
white-space: nowrap;
justify-content: flex-end;
}
div.dataTables_wrapper div.dataTables_processing {
position: absolute;
top: 50%;
left: 50%;
width: 200px;
margin-left: -100px;
margin-top: -26px;
text-align: center;
padding: 1em 0;
}
table.dataTable > thead > tr > th:active,
table.dataTable > thead > tr > td:active {
outline: none;
}
table.dataTable > thead > tr > th:not(.sorting_disabled),
table.dataTable > thead > tr > td:not(.sorting_disabled) {
padding-right: 30px;
}
table.dataTable > thead .sorting,
table.dataTable > thead .sorting_asc,
table.dataTable > thead .sorting_desc,
table.dataTable > thead .sorting_asc_disabled,
table.dataTable > thead .sorting_desc_disabled {
cursor: pointer;
position: relative;
}
table.dataTable > thead .sorting:before, table.dataTable > thead .sorting:after,
table.dataTable > thead .sorting_asc:before,
table.dataTable > thead .sorting_asc:after,
table.dataTable > thead .sorting_desc:before,
table.dataTable > thead .sorting_desc:after,
table.dataTable > thead .sorting_asc_disabled:before,
table.dataTable > thead .sorting_asc_disabled:after,
table.dataTable > thead .sorting_desc_disabled:before,
table.dataTable > thead .sorting_desc_disabled:after {
position: absolute;
bottom: 0.5em;
display: block;
opacity: 0.3;
}
table.dataTable > thead .sorting:before,
table.dataTable > thead .sorting_asc:before,
table.dataTable > thead .sorting_desc:before,
table.dataTable > thead .sorting_asc_disabled:before,
table.dataTable > thead .sorting_desc_disabled:before {
right: 1em;
content: "↑";
}
table.dataTable > thead .sorting:after,
table.dataTable > thead .sorting_asc:after,
table.dataTable > thead .sorting_desc:after,
table.dataTable > thead .sorting_asc_disabled:after,
table.dataTable > thead .sorting_desc_disabled:after {
right: 0.5em;
content: "↓";
}
table.dataTable > thead .sorting_asc:before,
table.dataTable > thead .sorting_desc:after {
opacity: 1;
}
table.dataTable > thead .sorting_asc_disabled:before,
table.dataTable > thead .sorting_desc_disabled:after {
opacity: 0;
}
div.dataTables_scrollHead table.dataTable {
margin-bottom: 0 !important;
@@ -280,17 +380,6 @@ div.dataTables_wrapper div.dataTables_paginate {
table.dataTable.table-sm > thead > tr > th:not(.sorting_disabled) {
padding-right: 20px;
}
table.dataTable.table-sm .sorting:before,
table.dataTable.table-sm .sorting_asc:before,
table.dataTable.table-sm .sorting_desc:before {
top: 5px;
right: 0.85em;
}
table.dataTable.table-sm .sorting:after,
table.dataTable.table-sm .sorting_asc:after,
table.dataTable.table-sm .sorting_desc:after {
top: 5px;
}
table.table-bordered.dataTable {
border-right-width: 0;
@@ -332,11 +421,4 @@ div.table-responsive > div.dataTables_wrapper > div.row > div[class^=col-]:last-
padding-right: 0;
}
table.dataTable.table-striped > tbody > tr:nth-of-type(2n+1) {
--bs-table-accent-bg: transparent;
}
table.dataTable.table-striped > tbody > tr.odd {
--bs-table-accent-bg: var(--bs-table-striped-bg);
}

File diff suppressed because it is too large Load Diff

View File

@@ -20,6 +20,13 @@
width: auto;
margin: -5px 0 0 0;
}
/* Special alert-row class to use Bootstrap v5.2+ variable colors */
.alert-row {
--bs-alert-border: 1px solid var(--bs-alert-border-color);
color: var(--bs-alert-color);
background-color: var(--bs-alert-bg);
border: var(--bs-alert-border);
}
</style>
<script src="{{urlpath}}/vw_static/identicon.js"></script>
<script>

View File

@@ -5,7 +5,7 @@
<div class="small text-white mb-3">
<span class="font-weight-bolder">NOTE:</span> The settings here override the environment variables. Once saved, it's recommended to stop setting them to avoid confusion.<br>
This does not apply to the read-only section, which can only be set via environment variables.<br>
Settings which are overridden are shown with <span class="is-overridden-true">double underscores</span>.
Settings which are overridden are shown with <span class="is-overridden-true alert-row px-1">a yellow colored background</span>.
</div>
<form class="form needs-validation" id="config-form" onsubmit="saveConfig(); return false;" novalidate>
@@ -16,7 +16,7 @@
<div id="g_{{group}}" class="card-body collapse">
{{#each elements}}
{{#if editable}}
<div class="row my-2 align-items-center is-overridden-{{overridden}}" title="[{{name}}] {{doc.description}}">
<div class="row my-2 align-items-center is-overridden-{{overridden}} alert-row" title="[{{name}}] {{doc.description}}">
{{#case type "text" "number" "password"}}
<label for="input_{{name}}" class="col-sm-3 col-form-label">{{doc.name}}</label>
<div class="col-sm-8">
@@ -71,16 +71,25 @@
{{#each config}}
{{#each elements}}
{{#unless editable}}
<div class="row my-2 align-items-center" title="[{{name}}] {{doc.description}}">
<div class="row my-2 align-items-center alert-row" title="[{{name}}] {{doc.description}}">
{{#case type "text" "number" "password"}}
<label for="input_{{name}}" class="col-sm-3 col-form-label">{{doc.name}}</label>
<div class="col-sm-8">
<div class="input-group">
<input readonly class="form-control" id="input_{{name}}" type="{{type}}"
value="{{value}}" {{#if default}} placeholder="Default: {{default}}" {{/if}}>
{{#case type "password"}}
{{!--
Also set the database_url input as password here.
If we would set it to password in config.rs it will not be character masked for the support string.
And sometimes this is more useful for providing support than just 3 asterisk.
--}}
{{#if (eq name "database_url")}}
<input readonly class="form-control" id="input_{{name}}" type="password" value="{{value}}" {{#if default}} placeholder="Default: {{default}}" {{/if}}>
<button class="btn btn-outline-secondary" type="button" onclick="toggleVis('input_{{name}}');">Show/hide</button>
{{/case}}
{{else}}
<input readonly class="form-control" id="input_{{name}}" type="{{type}}" value="{{value}}" {{#if default}} placeholder="Default: {{default}}" {{/if}}>
{{#case type "password"}}
<button class="btn btn-outline-secondary" type="button" onclick="toggleVis('input_{{name}}');">Show/hide</button>
{{/case}}
{{/if}}
</div>
</div>
{{/case}}
@@ -134,7 +143,9 @@
}
.is-overridden-true {
text-decoration: underline double;
--bs-alert-color: #664d03;
--bs-alert-bg: #fff3cd;
--bs-alert-border-color: #ffecb5;
}
</style>
@@ -238,19 +249,45 @@
return Array.from(form).some(el => 'origValue' in el.dataset && ( el.dataset.origValue !== el.value));
}
// Trigger Form Change Detection
// This function will prevent submitting a from when someone presses enter.
function preventFormSubmitOnEnter(form) {
form.onkeypress = function(e) {
let key = e.charCode || e.keyCode || 0;
if (key == 13) {
e.preventDefault();
}
}
}
// Initialize Form Change Detection
const config_form = document.getElementById('config-form');
initChangeDetection(config_form);
// Prevent enter to submitting the form and save the config.
// Users need to really click on save, this also to prevent accidental submits.
preventFormSubmitOnEnter(config_form);
// This function will hook into the smtp-test-email input field and will call the smtpTest() function when enter is pressed.
function submitTestEmailOnEnter() {
const smtp_test_email_input = document.getElementById('smtp-test-email');
smtp_test_email_input.onkeypress = function(e) {
let key = e.charCode || e.keyCode || 0;
if (key == 13) {
e.preventDefault();
smtpTest();
}
}
}
submitTestEmailOnEnter();
// Colorize some settings which are high risk
const risk_items = document.getElementsByClassName('col-form-label');
function colorRiskSettings(risk_el) {
Array.from(risk_el).forEach((el) => {
function colorRiskSettings() {
const risk_items = document.getElementsByClassName('col-form-label');
Array.from(risk_items).forEach((el) => {
if (el.innerText.toLowerCase().includes('risks') ) {
el.parentElement.className += ' alert-danger'
}
});
}
colorRiskSettings(risk_items);
colorRiskSettings();
</script>

View File

@@ -29,21 +29,56 @@ impl Fairing for AppHeaders {
}
}
async fn on_response<'r>(&self, _req: &'r Request<'_>, res: &mut Response<'r>) {
res.set_raw_header("Permissions-Policy", "accelerometer=(), ambient-light-sensor=(), autoplay=(), camera=(), encrypted-media=(), fullscreen=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), midi=(), payment=(), picture-in-picture=(), sync-xhr=(self \"https://haveibeenpwned.com\" \"https://2fa.directory\"), usb=(), vr=()");
async fn on_response<'r>(&self, req: &'r Request<'_>, res: &mut Response<'r>) {
res.set_raw_header("Permissions-Policy", "accelerometer=(), ambient-light-sensor=(), autoplay=(), battery=(), camera=(), display-capture=(), document-domain=(), encrypted-media=(), execution-while-not-rendered=(), execution-while-out-of-viewport=(), fullscreen=(), geolocation=(), gyroscope=(), keyboard-map=(), magnetometer=(), microphone=(), midi=(), payment=(), picture-in-picture=(), screen-wake-lock=(), sync-xhr=(), usb=(), web-share=(), xr-spatial-tracking=()");
res.set_raw_header("Referrer-Policy", "same-origin");
res.set_raw_header("X-Frame-Options", "SAMEORIGIN");
res.set_raw_header("X-Content-Type-Options", "nosniff");
// Obsolete in modern browsers, unsafe (XS-Leak), and largely replaced by CSP
res.set_raw_header("X-XSS-Protection", "0");
let csp = format!(
let req_uri_path = req.uri().path();
// Check if we are requesting an admin page, if so, allow unsafe-inline for scripts.
// TODO: In the future maybe we need to see if we can generate a sha256 hash or have no scripts inline at all.
let admin_path = format!("{}/admin", CONFIG.domain_path());
let mut script_src = "";
if req_uri_path.starts_with(admin_path.as_str()) {
script_src = " 'unsafe-inline'";
}
// Do not send the Content-Security-Policy (CSP) Header and X-Frame-Options for the *-connector.html files.
// This can cause issues when some MFA requests needs to open a popup or page within the clients like WebAuthn, or Duo.
// This is the same behaviour as upstream Bitwarden.
if !req_uri_path.ends_with("connector.html") {
// # Frame Ancestors:
// Chrome Web Store: https://chrome.google.com/webstore/detail/bitwarden-free-password-m/nngceckbapebfimnlniiiahkandclblb
// Edge Add-ons: https://microsoftedge.microsoft.com/addons/detail/bitwarden-free-password/jbkfoedolllekgbhcbcoahefnbanhhlh?hl=en-US
// Firefox Browser Add-ons: https://addons.mozilla.org/en-US/firefox/addon/bitwarden-password-manager/
"frame-ancestors 'self' chrome-extension://nngceckbapebfimnlniiiahkandclblb chrome-extension://jbkfoedolllekgbhcbcoahefnbanhhlh moz-extension://* {};",
CONFIG.allowed_iframe_ancestors()
);
res.set_raw_header("Content-Security-Policy", csp);
// # img/child/frame src:
// Have I Been Pwned and Gravator to allow those calls to work.
// # Connect src:
// Leaked Passwords check: api.pwnedpasswords.com
// 2FA/MFA Site check: 2fa.directory
// # Mail Relay: https://bitwarden.com/blog/add-privacy-and-security-using-email-aliases-with-bitwarden/
// app.simplelogin.io, app.anonaddy.com, relay.firefox.com
let csp = format!(
"default-src 'self'; \
script-src 'self'{script_src}; \
style-src 'self' 'unsafe-inline'; \
img-src 'self' data: https://haveibeenpwned.com/ https://www.gravatar.com; \
child-src 'self' https://*.duosecurity.com https://*.duofederal.com; \
frame-src 'self' https://*.duosecurity.com https://*.duofederal.com; \
connect-src 'self' https://api.pwnedpasswords.com/range/ https://2fa.directory/api/ https://app.simplelogin.io/api/ https://app.anonaddy.com/api/ https://relay.firefox.com/api/; \
object-src 'self' blob:; \
frame-ancestors 'self' chrome-extension://nngceckbapebfimnlniiiahkandclblb chrome-extension://jbkfoedolllekgbhcbcoahefnbanhhlh moz-extension://* {};",
CONFIG.allowed_iframe_ancestors()
);
res.set_raw_header("Content-Security-Policy", csp);
res.set_raw_header("X-Frame-Options", "SAMEORIGIN");
} else {
// It looks like this header get's set somewhere else also, make sure this is not sent for these files, it will cause MFA issues.
res.remove_header("X-Frame-Options");
}
// Disable cache unless otherwise specified
if !res.headers().contains("cache-control") {
@@ -265,7 +300,7 @@ impl Fairing for BetterLogging {
//
use std::{
fs::{self, File},
io::{Read, Result as IOResult},
io::Result as IOResult,
path::Path,
};
@@ -273,15 +308,6 @@ pub fn file_exists(path: &str) -> bool {
Path::new(path).exists()
}
pub fn read_file(path: &str) -> IOResult<Vec<u8>> {
let mut contents: Vec<u8> = Vec::new();
let mut file = File::open(Path::new(path))?;
file.read_to_end(&mut contents)?;
Ok(contents)
}
pub fn write_file(path: &str, content: &[u8]) -> Result<(), crate::error::Error> {
use std::io::Write;
let mut f = File::create(path)?;
@@ -290,15 +316,6 @@ pub fn write_file(path: &str, content: &[u8]) -> Result<(), crate::error::Error>
Ok(())
}
pub fn read_file_string(path: &str) -> IOResult<String> {
let mut contents = String::new();
let mut file = File::open(Path::new(path))?;
file.read_to_string(&mut contents)?;
Ok(contents)
}
pub fn delete_file(path: &str) -> IOResult<()> {
let res = fs::remove_file(path);