Compare commits

...

120 Commits

Author SHA1 Message Date
Daniel García
e3feba2a2c Merge pull request #960 from jjlin/admin-token
Warn on empty `ADMIN_TOKEN` instead of bailing out
2020-04-11 23:34:37 +02:00
Jeremy Lin
0a68de6c24 Warn on empty ADMIN_TOKEN instead of bailing out
The admin page will still be disabled.

Fixes #849.
2020-04-09 20:55:08 -07:00
Daniel García
4be8dae626 Make web vault show a more informative error when browsers block WebCrypto in insecure contexts and update dependencies 2020-04-09 22:54:31 +02:00
Daniel García
77f95146d6 Merge pull request #956 from jjlin/duo
Fix Duo auth failure with non-lowercased email addresses
2020-04-08 08:43:24 +02:00
Jeremy Lin
6cd8512bbd Fix Duo auth failure with non-lowercased email addresses 2020-04-07 20:40:51 -07:00
Daniel García
843604c9e7 Merge pull request #939 from jjlin/attachment-size
Fix attachment size limit calculation
2020-03-31 12:56:49 +02:00
Jeremy Lin
7407b8326a Fix attachment size limit calculation
The config values (in KB) need to be converted to bytes when comparing
against total attachment sizes.
2020-03-31 02:30:28 -07:00
Daniel García
adf47827c9 Make sure the data field is always returned, otherwise the mobile apps seem to have issues 2020-03-30 22:19:50 +02:00
Daniel García
5471088e93 Merge pull request #933 from jjlin/dockerfiles
Rebuild Dockerfiles to match latest Dockerfile.j2 template
2020-03-27 17:45:10 +01:00
Daniel García
4e85a1dee1 Update web vault to 2.13.2 2020-03-27 17:44:10 +01:00
Daniel García
ec60839064 Merge pull request #932 from jjlin/ws-fix
Fix WebSocket notifications
2020-03-27 08:38:54 +01:00
Jeremy Lin
d4bfa1a189 Rebuild Dockerfiles to match latest Dockerfile.j2 template
Picks up a couple of missed changes from b837348b and ccf6ee79.
2020-03-26 20:10:33 -07:00
Jeremy Lin
862d401077 Fix WebSocket notifications
Ignore a missing `id` query param; it's unclear what this ID represents,
but it wasn't being used in the existing bitwarden_rs code, and no longer
seems to be sent in the latest versions of the official clients.
2020-03-26 19:26:44 -07:00
Daniel García
255a06382d Merge pull request #928 from jjlin/healthcheck
Healthcheck fixes/optimizations
2020-03-26 21:13:31 +01:00
Jeremy Lin
bbb0484d03 Healthcheck fixes/optimizations
* Switch healthcheck interval/timeout from 30s/3s to 60s/10s.
  30s interval is arguably overkill, and 3s timeout is definitely too short
  for lower end machines.
* Use HEALTHCHECK CMD exec form to avoid superfluous `sh` invocations.
* Add `--silent --show-error` flags to curl call to avoid progress meter being
  shown in healthcheck logs.
2020-03-25 20:13:36 -07:00
Daniel García
93346bc05d Merge pull request #927 from jjlin/healthcheck
Update healthcheck script to handle alternate base dir
2020-03-25 22:21:08 +01:00
Jeremy Lin
fdf50f0064 Update healthcheck script to handle alternate base dir 2020-03-24 20:00:35 -07:00
Daniel García
ccf6ee79d0 Update dependencies, mainly diesel and sqlite 2020-03-24 20:36:19 +01:00
Daniel García
91dd19473d Merge pull request #922 from jjlin/device-push-token
Handle `devicePushToken`
2020-03-23 00:03:10 +01:00
Jeremy Lin
c06162b22f Handle devicePushToken
Mobile push isn't currently supported, but this should get rid of spurious
`Detected unexpected parameter during login: devicepushtoken` warnings.
2020-03-22 15:04:25 -07:00
Daniel García
7a6a3e4160 Set the cargo version and allow changing it during build time with BWRS_VERSION.
Also renamed GIT_VERSION because that's not the only source anymore.
2020-03-22 16:13:34 +01:00
Daniel García
94341f9f3f Fix token error while accepting invite 2020-03-20 10:51:17 +01:00
Daniel García
ff19fb3426 Merge pull request #919 from BlackDex/issue-908
Fixed issue #908
2020-03-19 18:11:47 +01:00
BlackDex
baac8d9627 Fixed issue #908
The organization uuid is most of the time within the uri path as a
parameter. But sometimes it only is there as a query value.

This fix checks both, and returns the uuid when possible.
2020-03-19 17:37:10 +01:00
BlackDex
669b101e6a Fixing issue #908
Sometimes an org-uuid is not within the path but in a query value,
This fixes the check for that.
2020-03-19 16:50:47 +01:00
Daniel García
935f38692f Merge pull request #918 from dani-garcia/revert-901-feature/opportunistic_tls
Revert "Use opportunistic TLS in SMTP connections"
2020-03-19 13:58:00 +01:00
Daniel García
d2d9fb08cc Revert "Use opportunistic TLS in SMTP connections" 2020-03-19 13:56:53 +01:00
Daniel García
b85d548879 Merge pull request #916 from BlackDex/issue-759
Fixing issue #759 by disabling Foreign Key Checks.
2020-03-18 18:48:08 +01:00
BlackDex
35f30088b2 Fixing issue #759 by disabling Foreign Key Checks.
During migrations some queries are out of order regarding to foreign
keys.
Because of this the migrations fail when the sql database has this
enforced by default.
Turning of this check during the migrations will fix this and this is
only per session.
2020-03-18 18:11:11 +01:00
Daniel García
dce054e632 Merge pull request #912 from ymage/openssl_as_default
Fix alpine build with openssl crate as default
2020-03-16 23:02:07 +01:00
Ymage
ba725e1c25 Make openssl crate as default (non feature-flipped) 2020-03-16 22:39:10 +01:00
Ymage
b837348b25 Build as static 2020-03-16 22:34:59 +01:00
Daniel García
7d9c7017c9 Merge pull request #911 from BlackDex/upgrade-rocket
Upgrade rocket
2020-03-16 18:17:17 +01:00
Daniel García
d6b9b8bf0c Merge pull request #876 from BlackDex/log-panics
Make panics logable (as warn)
2020-03-16 18:16:49 +01:00
BlackDex
bd09fe1a3d Updated code so backtraces are logged also. 2020-03-16 17:53:22 +01:00
BlackDex
bcbe6177b8 Merge branch 'master' of https://github.com/dani-garcia/bitwarden_rs into log-panics 2020-03-16 17:19:27 +01:00
BlackDex
9b1d07365e Updated ring
Some small changes to match the updated ring package.
2020-03-16 16:39:20 +01:00
BlackDex
37b212427c Updated jsonwebtoken
Updated to the latest version of jsonwebtoken.
Some small code changes to match the new versions.
2020-03-16 16:38:00 +01:00
BlackDex
078234d8b3 Small change for rocket compatibilty 2020-03-16 16:36:44 +01:00
BlackDex
3ce0c3d1a5 Update dependencies
Primarily updating rocket, which needed some dependencies

Latest versions of:
 - ring
 - time
 - jsonwebtoken
 - yubico
 - rocket (git)
2020-03-16 16:32:33 +01:00
Daniel García
2ee07ea1d8 Fix empty data when cloning cipher 2020-03-15 17:26:34 +01:00
Daniel García
40c339db9b Fix postgres policies, second try 2020-03-14 23:53:12 +01:00
Daniel García
402c1cd06c Merge pull request #906 from BlackDex/upgrade-reqwest
Updated reqwest to the latest version.
2020-03-14 23:35:52 +01:00
Daniel García
819f340f39 Fix issue with postgres 2020-03-14 23:35:34 +01:00
BlackDex
1b4b40c95d Updated reqwest to the latest version.
- Use the blocking client (no async).
- Disabled gzip.
- use_sys_proxy is now default.
2020-03-14 23:12:45 +01:00
Daniel García
afd9f4e278 Allow the smtp mechanism to be provided without quotes and all lowercase 2020-03-14 22:31:41 +01:00
Daniel García
47a9461f39 Merge pull request #903 from TheBinaryLoop/patch-1
Updated domains with new values vualt
2020-03-14 14:41:39 +01:00
Daniel García
c6f64d8368 Merge pull request #901 from sleweke/feature/opportunistic_tls
Use opportunistic TLS in SMTP connections
2020-03-14 14:41:00 +01:00
Daniel García
edabf19ddf Update vault to 2.13.1 2020-03-14 14:40:06 +01:00
Daniel García
a30d5f4cf9 Fix cloning issues 2020-03-14 14:08:57 +01:00
Daniel García
3fa78e7bb1 Initial version of policies 2020-03-14 13:32:28 +01:00
Lukas Eßmann
a8a7e4f9a5 Updated domains with new values vualt
Added domains from official vault.bitwarden.com
2020-03-13 20:08:52 +01:00
Samuel Leweke
5d3b765a23 Use opportunistic TLS in SMTP connections
If SSL is disabled, the SMTP ClientSecurity of the lettre crate
defaults to None, that is, an insecure connection. This is changed to
Opportunistic, which uses TLS if available. If TLS is not available,
the insecure connection is used (i.e., this change is backward
compatible).
2020-03-12 11:40:52 +01:00
Daniel García
70f3ab8ec3 Migrate lazy_static to once_cell, less macro magic and slightly faster 2020-03-09 22:04:03 +01:00
Daniel García
b6612e90ca Update dependencies 2020-03-09 22:00:59 +01:00
Daniel García
161cccca30 Merge pull request #892 from BlackDex/smtp-test-button
Relocated SMTP test input+button.
2020-03-05 00:05:52 +01:00
BlackDex
84dc2eda1f Changed javascript default argument construction 2020-03-04 15:08:14 +01:00
BlackDex
390d10d656 Relocated SMTP test input+button.
- Moved smtp test option to within the "SMTP Email" Settings block.
- Added optional option to prevent full page reload.
- SMTP Test and Backup do not reload the admin interface any more.
2020-03-04 13:25:38 +01:00
Daniel García
1f775f4414 Merge pull request #888 from zethra/add_cli_args
Added command line flags for help and version
2020-03-03 00:12:15 +01:00
zethra
cc404b4edc Added command line flags for help and version
Signed-off-by: zethra <benaagoldberg@gmail.com>
2020-03-02 15:51:57 -05:00
Daniel García
536672ac1b Delete ISSUE_TEMPLATE.md 2020-03-02 19:58:53 +01:00
Daniel García
e41e7c07db Update issue templates 2020-03-02 19:58:36 +01:00
Daniel García
f1d3b03c60 Update README.md 2020-03-02 19:37:49 +01:00
Daniel García
2ebff958a4 Merge pull request #879 from BlackDex/smtp-test-button
Added SMTP test button in the admin gui
2020-03-01 15:00:20 +01:00
Daniel García
edfdda86ae Use web vault built by docker autobuild, using the hash to reference the image for extra security 2020-03-01 02:40:18 +01:00
BlackDex
97fb7b5b96 Added urlpath to smtpTest function 2020-02-26 16:58:57 +01:00
BlackDex
f6de144cbb Merge branch 'smtp-test-button' of github.com:BlackDex/bitwarden_rs into smtp-test-button 2020-02-26 16:56:03 +01:00
BlackDex
5a974c7b94 Added SMTP test button in the admin gui
- Added a test button for checking the e-mail settings.
- Fixed a bug with the _post JavaScript function:
  A function was overwriten with a variable and errors were not handled
correctly like a 500 for example.
2020-02-26 16:49:56 +01:00
BlackDex
5f61607419 Added SMTP test button in the admin gui
- Added a test button for checking the e-mail settings.
- Fixed a bug with the _post JavaScript function:
  A function was overwriten with a variable and errors were not handled
correctly like a 500 for example.
2020-02-26 11:02:22 +01:00
BlackDex
7439aeb63e Make panics logable (as warn)
panic!()'s only appear on stderr, this makes tracking down some strange
issues harder with the usage of docker since stderr does not get logged
into the bitwarden.log file. This change logs the message to stdout and
the logfile when activated.
2020-02-25 14:10:52 +01:00
Daniel García
cd8907542a Make sure the provided domain contains the protocol and show a useful error when it doesn't 2020-02-23 14:55:27 +01:00
Daniel García
8a5450e830 Merge pull request #868 from jjlin/alt-base
Add backend support for alternate base dir (subdir/subpath) hosting
2020-02-22 22:06:07 +01:00
Daniel García
ad9f2b2d8e Removed test urlpath 2020-02-22 19:01:58 +01:00
Daniel García
2f4a9865e1 Use absolute paths in the admin page 2020-02-22 17:49:33 +01:00
Daniel García
0a3008e753 Update web vault used in docker 2020-02-22 16:00:43 +01:00
Jeremy Lin
29a0795219 Add backend support for alternate base dir (subdir/subpath) hosting
To use this, include a path in the `DOMAIN` URL, e.g.:

* `DOMAIN=https://example.com/custom-path`
* `DOMAIN=https://example.com/multiple/levels/are/ok`
2020-02-18 21:27:00 -08:00
Daniel García
63459c5f72 Updated FUNDING as mentioned in #859 2020-02-18 21:48:11 +01:00
Daniel García
916e96b143 Update web vault to fix copy issues 2020-02-18 20:08:21 +01:00
Daniel García
325039c316 Attachment size limits, per-user and per-organization 2020-02-17 22:56:26 +01:00
Daniel García
c5b97f4146 Merge pull request #864 from mprasil/admin-invitation
Do not disable invitations via admin API
2020-02-16 22:12:00 +01:00
Miro Prasil
03233429f4 Remove check from Invitation:take()
I've checked the spots when `Invitation::new()` and `Invitation::take()`
are used and it seems like all spots are already correctly gated. So to
enable invitations via admin API even when invitations are otherwise
disabled, this check can be removed.
2020-02-16 20:28:50 +00:00
Miroslav Prasil
0a72c4b6db Do not disable invitations via admin API
This was brought up today:

https://github.com/dani-garcia/bitwarden_rs/issues/752#issuecomment-586715073

I don't think it makes much sense in checking whether admin has the
right to send invitation as admin can change the setting anyway.

Removing the condition allows users to forbid regular users from
inviting new users to server while still preserving the option to do so
via the admin API.
2020-02-16 15:01:07 +00:00
Daniel García
8867626de8 Add option to change invitation org name, fixes #825
Add option to allow additional iframe ancestors, fixes #843
Sort the rocket routes before printing them
2020-02-04 22:14:50 +01:00
Daniel García
f5916ec396 Fix backwards indices 2020-01-30 22:33:50 +01:00
Daniel García
ebb36235a7 Cache icons in the clients 2020-01-30 22:30:57 +01:00
Daniel García
def174a517 Convert email domains to punycode 2020-01-30 22:11:53 +01:00
Daniel García
2798f623d4 Updated rust toolchain version 2020-01-30 22:11:44 +01:00
Daniel García
480ba933fa Don't error if admin token is empty but disabled 2020-01-30 22:10:50 +01:00
Daniel García
3d1ee9ef62 Use rust-toolchain file to determine version in workflows, disabled mac builds for now 2020-01-29 19:26:06 +01:00
Daniel García
5352321fe1 Merge pull request #831 from mprasil/whitelist-fix
SIGNUPS_ALLOWED with no whitelist [fixes #830]
2020-01-29 13:28:07 +01:00
Miro Prasil
c4101162d6 SIGNUPS_ALLOWED with no whitelist [fixes #830]
This reverts back to `SIGNUPS_ALLOWED` when there is no domain whitelist
set. The functionality was broken in 64d6f72.
2020-01-29 11:32:42 +00:00
Daniel García
632d55265b Merge pull request #824 from tomuta/fix_change_email
Fix change email when no whitelist is configured
2020-01-28 20:52:16 +01:00
tomuta
e277f7d1c1 Fix change email when no whitelist is configured
Fixes issue #792
2020-01-26 13:34:56 -07:00
Daniel García
ff7b4a3d38 Update handlebars to 3.0 which included performance improvements.
Updated lettre to newer git revision, which should give better error messages now.
2020-01-26 15:29:14 +01:00
Daniel García
d212dfe735 Accept y/n, True/False, 1/0 as booleans in environment vars 2020-01-20 22:28:54 +01:00
Daniel García
84ed185579 Update u2f to 0.2, which requires OpenSSL but also might solve the problems we've had with certificates.
The rust image doesn't need installing curl or tar, so removed. Also collapsed ENV lines.
2020-01-19 21:34:13 +01:00
Daniel García
c0ba3406ef Merge pull request #812 from swedishborgie/postgresql
Fixes #635 - Unique constraint violation when using U2F tokens on PostgreSQL
2020-01-16 16:21:57 +01:00
Michael Powers
e196ba6e86 Switch error handling to ? operator instead of explicit handling. 2020-01-16 08:14:25 -05:00
Michael Powers
76743aee48 Fixes #635 - Unique constraint violation when using U2F tokens on PostgreSQL
Because of differences in how .on_conflict() works compared to .replace_into() the PostgreSQL backend wasn't correctly ensuring the unique constraint on user_uuid and atype wasn't getting violated.

This change simply issues a DELETE on the unique constraint prior to the insert to ensure uniqueness. PostgreSQL does not support multiple constraints in ON CONFLICT clauses.
2020-01-13 21:53:57 -05:00
Daniel García
9ebca99290 Update dependencies 2020-01-10 18:37:16 +01:00
Daniel García
a734ad2d36 Add contributor 2020-01-10 18:36:36 +01:00
Daniel García
baf7d1be4e Delete old workflow file and disable building binaries on pull requests, as we already have CI for that 2020-01-05 22:46:34 +01:00
Daniel García
31bcd1bf7c Merge pull request #784 from ypid/docker/use-debian-base
Use Debian base image for all steps of the build process
2020-01-05 22:42:43 +01:00
Daniel García
a3b30ed65a Add missing target armv7 and cross compile envs 2020-01-05 22:41:58 +01:00
Robin Schneider
402c857d17 Add hint to Dockerfile's that they are generated 2020-01-03 22:07:56 +01:00
Robin Schneider
def858854b Readd missing cargo build for armv7. Thanks to @dani-garcia! 2020-01-03 22:00:45 +01:00
Robin Schneider
f6761ac30e Remove debugging echo statement from Dockerfiles 2020-01-01 15:17:33 +01:00
Robin Schneider
f8e49ea3f4 Use apt-get instead of apt in Dockerfiles, also --no-install-recommends
apt is intended for humans, not scripts.

--no-install-recommends improves build time by avoiding to install
unneeded packages.
2019-12-31 16:46:08 +01:00
Robin Schneider
f6a4a2127b Remove duplicate empty lines in generated Dockerfiles
Checked with:

```Shell
find . -type f -print0 | xargs -0 pcregrep -M '\n\n\n'
```
2019-12-31 16:33:00 +01:00
Robin Schneider
446fc3f1f8 Set build time options for dpkg and reproducible builds
Ref: https://github.com/moby/moby/issues/4032
Ref: https://sweetcode.io/using-docker-reproducible-build-environments/
Ref: https://github.com/hashbang/aosp-build/blob/master/config/container/Dockerfile
2019-12-31 16:33:00 +01:00
Robin Schneider
146525db91 Improve Jinja2 template logic a bit 2019-12-31 16:33:00 +01:00
Robin Schneider
1698b43f9b Readd missing cargo setup for armv7. Thanks to @dani-garcia! 2019-12-31 16:33:00 +01:00
Robin Schneider
078b21db85 Fix armv6 build, thanks to @dani-garcia for the review! 2019-12-31 16:33:00 +01:00
Robin Schneider
43adcde094 Move rustup target before cargo build. Thanks to @dani-garcia!
Note from @dani-garcia:

> I don't think this is doing anything right now because the target is probably
> installed already.
2019-12-31 16:32:59 +01:00
Daniel García
7a0bb18dcf Make cargo new independent of workdir to be exact
The muslrust images seem to have a workdir of /volume as opposed to / in the
others so doing cargo new like this would create the folder in /volume/app.
2019-12-31 16:32:59 +01:00
Robin Schneider
47a5a4e1fc Fix package name for Ubuntu 16.04 based image. Thanks @dani-garcia! 2019-12-31 16:32:59 +01:00
Robin Schneider
0f0e5876ae Move dpkg --add-architecture before the first apt call
Thanks to @dani-garcia for the review!
2019-12-31 16:32:59 +01:00
Robin Schneider
43aa75dc89 Fix cross platform build support, thanks to @dani-garcia for the review 2019-12-31 16:32:59 +01:00
Robin Schneider
8280d200ea Generate Dockerfiles from one source for maintainability. Closes #785. 2019-12-28 22:52:20 +01:00
Robin Schneider
f250c54813 WIP: Use Debian base image for all steps of the build process
No need to use two different base images. Debian buster is pulled later
anyway so we can just use it for the vault stage as well.

My reason for this change is partly to avoid redundancy and partly to
make it easier to build everything yourself. When all the build
environment is based on Debian than you just have to figure out how to
build a Debian Docker base image (ref:
https://github.com/ypid/docker-makefile).
2019-12-28 14:43:08 +01:00
80 changed files with 3706 additions and 2184 deletions

1
.github/FUNDING.yml vendored
View File

@@ -1 +1,2 @@
github: dani-garcia
custom: ["https://paypal.me/DaniGG"]

View File

@@ -1,3 +1,12 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
<!--
Please fill out the following template to make solving your problem easier and faster for us.
This is only a guideline. If you think that parts are unneccessary for your issue, feel free to remove them.

View File

@@ -0,0 +1,11 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: better for forum
assignees: ''
---
# Please submit all your feature requests to the forum
Link: https://bitwardenrs.discourse.group/c/feature-requests

View File

@@ -0,0 +1,11 @@
---
name: Help with installation/configuration
about: Any questions about the setup of bitwarden_rs
title: ''
labels: better for forum
assignees: ''
---
# Please submit all your third party help requests to the forum
Link: https://bitwardenrs.discourse.group/c/help

View File

@@ -0,0 +1,11 @@
---
name: Help with proxy/database/NAS setup
about: Any questions about third party software
title: ''
labels: better for forum
assignees: ''
---
# Please submit all your third party help requests to the forum
Link: https://bitwardenrs.discourse.group/c/third-party-help

View File

@@ -1,70 +0,0 @@
name: build-windows
on: [push, pull_request]
jobs:
build:
runs-on: windows-latest
strategy:
matrix:
db-backend: [sqlite, mysql, postgresql]
steps:
- uses: actions/checkout@v1
- name: Cache choco cache
uses: actions/cache@v1.0.3
with:
path: ~\AppData\Local\Temp\chocolatey
key: ${{ runner.os }}-choco-cache
- name: Install dependencies
run: choco install openssl sqlite postgresql12 mysql
- name: Cache cargo registry
uses: actions/cache@v1.0.3
with:
path: ~/.cargo/registry
key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
- name: Cache cargo index
uses: actions/cache@v1.0.3
with:
path: ~/.cargo/git
key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }}
- name: Cache cargo build
uses: actions/cache@v1.0.3
with:
path: target
key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }}
- name: Install latest nightly
uses: actions-rs/toolchain@v1
with:
toolchain: nightly
override: true
profile: minimal
target: x86_64-pc-windows-msvc
- name: Build
run: cargo.exe build --verbose --features ${{ matrix.db-backend }} --release --target x86_64-pc-windows-msvc
env:
OPENSSL_DIR: C:\Program Files\OpenSSL-Win64\
- name: Run tests
run: cargo test --features ${{ matrix.db-backend }}
- name: Upload windows artifact
uses: actions/upload-artifact@v1.0.0
with:
name: x86_64-pc-windows-msvc-${{ matrix.db-backend }}-bitwarden_rs
path: target/release/bitwarden_rs.exe
- name: Release
uses: Shopify/upload-to-release@1.0.0
if: startsWith(github.ref, 'refs/tags/')
with:
name: x86_64-pc-windows-msvc-${{ matrix.db-backend }}-bitwarden_rs
path: target/release/bitwarden_rs.exe
repo-token: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -4,9 +4,9 @@ on:
push:
paths-ignore:
- "**.md"
pull_request:
paths-ignore:
- "**.md"
#pull_request:
# paths-ignore:
# - "**.md"
jobs:
build:
@@ -18,7 +18,7 @@ jobs:
target:
- x86_64-unknown-linux-gnu
# - x86_64-unknown-linux-musl
- x86_64-apple-darwin
# - x86_64-apple-darwin
# - x86_64-pc-windows-msvc
include:
- target: x86_64-unknown-linux-gnu
@@ -27,9 +27,9 @@ jobs:
# - target: x86_64-unknown-linux-musl
# os: ubuntu-latest
# ext:
- target: x86_64-apple-darwin
os: macOS-latest
ext:
# - target: x86_64-apple-darwin
# os: macOS-latest
# ext:
# - target: x86_64-pc-windows-msvc
# os: windows-latest
# ext: .exe
@@ -110,10 +110,9 @@ jobs:
key: ${{ runner.os }}-${{matrix.db-backend}}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }}
- name: Install latest nightly
uses: actions-rs/toolchain@v1
uses: actions-rs/toolchain@v1.0.5
with:
toolchain: nightly
override: true
# Uses rust-toolchain to determine version
profile: minimal
target: ${{ matrix.target }}

3045
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -14,7 +14,7 @@ build = "build.rs"
# Empty to keep compatibility, prefer to set USE_SYSLOG=true
enable_syslog = []
mysql = ["diesel/mysql", "diesel_migrations/mysql"]
postgresql = ["diesel/postgres", "diesel_migrations/postgres", "openssl"]
postgresql = ["diesel/postgres", "diesel_migrations/postgres"]
sqlite = ["diesel/sqlite", "diesel_migrations/sqlite", "libsqlite3-sys"]
[target."cfg(not(windows))".dependencies]
@@ -26,7 +26,7 @@ rocket = { version = "0.5.0-dev", features = ["tls"], default-features = false }
rocket_contrib = "0.5.0-dev"
# HTTP client
reqwest = "0.9.24"
reqwest = { version = "0.10.4", features = ["blocking", "json"] }
# multipart/form-data support
multipart = { version = "0.16.1", features = ["server"], default-features = false }
@@ -35,92 +35,99 @@ multipart = { version = "0.16.1", features = ["server"], default-features = fals
ws = "0.9.1"
# MessagePack library
rmpv = "0.4.3"
rmpv = "0.4.4"
# Concurrent hashmap implementation
chashmap = "2.2.2"
# A generic serialization/deserialization framework
serde = "1.0.104"
serde_derive = "1.0.104"
serde_json = "1.0.44"
serde = "1.0.106"
serde_derive = "1.0.106"
serde_json = "1.0.51"
# Logging
log = "0.4.8"
fern = { version = "0.5.9", features = ["syslog-4"] }
fern = { version = "0.6.0", features = ["syslog-4"] }
# A safe, extensible ORM and Query builder
diesel = { version = "1.4.3", features = [ "chrono", "r2d2"] }
diesel = { version = "1.4.4", features = [ "chrono", "r2d2"] }
diesel_migrations = "1.4.0"
# Bundled SQLite
libsqlite3-sys = { version = "0.16.0", features = ["bundled"], optional = true }
libsqlite3-sys = { version = "0.17.3", features = ["bundled"], optional = true }
# Crypto library
ring = "0.14.6"
ring = "0.16.12"
# UUID generation
uuid = { version = "0.8.1", features = ["v4"] }
# Date and time library for Rust
chrono = "0.4.10"
# Date and time librar for Rust
chrono = "0.4.11"
time = "0.2.9"
# TOTP library
oath = "0.10.2"
# Data encoding library
data-encoding = "2.1.2"
data-encoding = "2.2.0"
# JWT library
jsonwebtoken = "6.0.1"
jsonwebtoken = "7.1.0"
# U2F library
u2f = "0.1.6"
u2f = "0.2.0"
# Yubico Library
yubico = { version = "0.7.1", features = ["online-tokio"], default-features = false }
yubico = { version = "0.9.0", features = ["online-tokio"], default-features = false }
# A `dotenv` implementation for Rust
dotenv = { version = "0.15.0", default-features = false }
# Lazy static macro
lazy_static = "1.4.0"
# Lazy initialization
once_cell = "1.3.1"
# More derives
derive_more = "0.99.2"
derive_more = "0.99.5"
# Numerical libraries
num-traits = "0.2.10"
num-traits = "0.2.11"
num-derive = "0.3.0"
# Email libraries
lettre = "0.9.2"
lettre_email = "0.9.2"
native-tls = "0.2.3"
quoted_printable = "0.4.1"
lettre = "0.10.0-pre"
native-tls = "0.2.4"
quoted_printable = "0.4.2"
# Template library
handlebars = "=2.0.2"
handlebars = { version = "3.0.1", features = ["dir_source"] }
# For favicon extraction from main website
soup = "0.4.1"
regex = "1.3.1"
soup = "0.5.0"
regex = "1.3.6"
data-url = "0.1.0"
# Required for SSL support for PostgreSQL
openssl = { version = "0.10.26", optional = true }
# Used by U2F, JWT and Postgres
openssl = "0.10.29"
# URL encoding library
percent-encoding = "2.1.0"
# Punycode conversion
idna = "0.2.0"
# CLI argument parsing
structopt = "0.3.13"
# Logging panics to logfile instead stderr only
backtrace = "0.3.46"
[patch.crates-io]
# Use newest ring
rocket = { git = 'https://github.com/SergioBenitez/Rocket', rev = 'b95b6765e1cc8be7c1e7eaef8a9d9ad940b0ac13' }
rocket_contrib = { git = 'https://github.com/SergioBenitez/Rocket', rev = 'b95b6765e1cc8be7c1e7eaef8a9d9ad940b0ac13' }
rocket = { git = 'https://github.com/SergioBenitez/Rocket', rev = 'dfc9e9aab01d349da32c52db393e35b7fffea63c' }
rocket_contrib = { git = 'https://github.com/SergioBenitez/Rocket', rev = 'dfc9e9aab01d349da32c52db393e35b7fffea63c' }
# Use git version for timeout fix #706
lettre = { git = 'https://github.com/lettre/lettre', rev = '24d694db3be017d82b1cdc8bf9da601420b31bb0' }
lettre_email = { git = 'https://github.com/lettre/lettre', rev = '24d694db3be017d82b1cdc8bf9da601420b31bb0' }
lettre = { git = 'https://github.com/lettre/lettre', rev = '245c600c82ee18b766e8729f005ff453a55dce34' }
# For favicon extraction from main website
data-url = { git = 'https://github.com/servo/rust-url', package="data-url", rev = '7f1bd6ce1c2fde599a757302a843a60e714c5f72' }

View File

@@ -13,7 +13,7 @@ Image is based on [Rust implementation of Bitwarden API](https://github.com/dani
**This project is not associated with the [Bitwarden](https://bitwarden.com/) project nor 8bit Solutions LLC.**
#### ⚠️**IMPORTANT**⚠️: When using this server, please report any Bitwarden related bug-reports or suggestions [here](https://github.com/dani-garcia/bitwarden_rs/issues/new), regardless of whatever clients you are using (mobile, desktop, browser...). DO NOT use the official support channels.
#### ⚠️**IMPORTANT**⚠️: When using this server, please report any bugs or suggestions to us directly (look at the bottom of this page for ways to get in touch), regardless of whatever clients you are using (mobile, desktop, browser...). DO NOT use the official support channels.
---
@@ -21,14 +21,14 @@ Image is based on [Rust implementation of Bitwarden API](https://github.com/dani
Basically full implementation of Bitwarden API is provided including:
* Basic single user functionality
* Single user functionality
* Organizations support
* Attachments
* Vault API support
* Serving the static files for Vault interface
* Website icons API
* Authenticator and U2F support
* YubiKey OTP
* YubiKey and Duo support
## Installation
Pull the docker image and mount a volume from the host for persistent storage:
@@ -49,12 +49,13 @@ If you have an available domain name, you can get HTTPS certificates with [Let's
See the [bitwarden_rs wiki](https://github.com/dani-garcia/bitwarden_rs/wiki) for more information on how to configure and run the bitwarden_rs server.
## Get in touch
To ask a question, offer suggestions or new features or to get help configuring or installing the software, please [use the forum](https://bitwardenrs.discourse.group/).
To ask a question, [raising an issue](https://github.com/dani-garcia/bitwarden_rs/issues/new) is fine. Please also report any bugs spotted here.
If you spot any bugs or crashes with bitwarden_rs itself, please [create an issue](https://github.com/dani-garcia/bitwarden_rs/issues/). Make sure there aren't any similar issues open, though!
If you prefer to chat, we're usually hanging around at [#bitwarden_rs:matrix.org](https://matrix.to/#/#bitwarden_rs:matrix.org) room on Matrix. Feel free to join us!
### Sponsors
Thanks for your contribution to the project!
- [@Skaronator](https://github.com/Skaronator)
- [@ChonoN](https://github.com/ChonoN)

View File

@@ -1,4 +1,5 @@
use std::process::Command;
use std::env;
fn main() {
#[cfg(all(feature = "sqlite", feature = "mysql"))]
@@ -11,8 +12,13 @@ fn main() {
#[cfg(not(any(feature = "sqlite", feature = "mysql", feature = "postgresql")))]
compile_error!("You need to enable one DB backend. To build with previous defaults do: cargo build --features sqlite");
if let Ok(version) = env::var("BWRS_VERSION") {
println!("cargo:rustc-env=BWRS_VERSION={}", version);
println!("cargo:rustc-env=CARGO_PKG_VERSION={}", version);
} else {
read_git_info().ok();
}
}
fn run(args: &[&str]) -> Result<String, std::io::Error> {
let out = Command::new(args[0]).args(&args[1..]).output()?;
@@ -54,14 +60,16 @@ fn read_git_info() -> Result<(), std::io::Error> {
} else {
format!("{}-{}", last_tag, rev_short)
};
println!("cargo:rustc-env=GIT_VERSION={}", version);
println!("cargo:rustc-env=BWRS_VERSION={}", version);
println!("cargo:rustc-env=CARGO_PKG_VERSION={}", version);
// To access these values, use:
// env!("GIT_EXACT_TAG")
// env!("GIT_LAST_TAG")
// env!("GIT_BRANCH")
// env!("GIT_REV")
// env!("GIT_VERSION")
// env!("BWRS_VERSION")
Ok(())
}

296
docker/Dockerfile.j2 Normal file
View File

@@ -0,0 +1,296 @@
# This file was generated using a Jinja2 template.
# Please make your changes in `Dockerfile.j2` and then `make` the individual Dockerfile's.
{% set build_stage_base_image = "rust:1.40" %}
{% if "alpine" in target_file %}
{% set build_stage_base_image = "clux/muslrust:nightly-2020-03-09" %}
{% set runtime_stage_base_image = "alpine:3.11" %}
{% set package_arch_name = "" %}
{% elif "amd64" in target_file %}
{% set runtime_stage_base_image = "debian:buster-slim" %}
{% set package_arch_name = "" %}
{% elif "aarch64" in target_file %}
{% set runtime_stage_base_image = "balenalib/aarch64-debian:buster" %}
{% set package_arch_name = "arm64" %}
{% elif "armv6" in target_file %}
{% set runtime_stage_base_image = "balenalib/rpi-debian:buster" %}
{% set package_arch_name = "armel" %}
{% elif "armv7" in target_file %}
{% set runtime_stage_base_image = "balenalib/armv7hf-debian:buster" %}
{% set package_arch_name = "armhf" %}
{% endif %}
{% set package_arch_prefix = ":" + package_arch_name %}
{% if package_arch_name == "" %}
{% set package_arch_prefix = "" %}
{% endif %}
# Using multistage build:
# https://docs.docker.com/develop/develop-images/multistage-build/
# https://whitfin.io/speeding-up-rust-docker-builds/
####################### VAULT BUILD IMAGE #######################
{% set vault_image_hash = "sha256:f32c555a2bc3ee6bc0718319b1e8057c10ef889cf7231f0ff217af98486da554" %}
{% raw %}
# This hash is extracted from the docker web-vault builds and it's prefered over a simple tag because it's immutable.
# It can be viewed in multiple ways:
# - From the https://hub.docker.com/repository/docker/bitwardenrs/web-vault/tags page, click the tag name and the digest should be there.
# - From the console, with the following commands:
# docker pull bitwardenrs/web-vault:v2.13.2b
# docker image inspect --format "{{.RepoDigests}}" bitwardenrs/web-vault:v2.13.2b
#
# - To do the opposite, and get the tag from the hash, you can do:
# docker image inspect --format "{{.RepoTags}}" bitwardenrs/web-vault@sha256:f32c555a2bc3ee6bc0718319b1e8057c10ef889cf7231f0ff217af98486da554
{% endraw %}
FROM bitwardenrs/web-vault@{{ vault_image_hash }} as vault
########################## BUILD IMAGE ##########################
{% if "musl" in build_stage_base_image %}
# Musl build image for statically compiled binary
{% else %}
# We need to use the Rust build image, because
# we need the Rust compiler and Cargo tooling
{% endif %}
FROM {{ build_stage_base_image }} as build
{% if "sqlite" in target_file %}
# set sqlite as default for DB ARG for backward compatibility
ARG DB=sqlite
{% elif "mysql" in target_file %}
# set mysql backend
ARG DB=mysql
{% elif "postgresql" in target_file %}
# set postgresql backend
ARG DB=postgresql
{% endif %}
# Build time options to avoid dpkg warnings and help with reproducible builds.
ENV DEBIAN_FRONTEND=noninteractive LANG=C.UTF-8 TZ=UTC TERM=xterm-256color
# Don't download rust docs
RUN rustup set profile minimal
{% if "alpine" in target_file %}
ENV USER "root"
ENV RUSTFLAGS='-C link-arg=-s'
{% elif "aarch64" in target_file or "armv" in target_file %}
# Install required build libs for {{ package_arch_name }} architecture.
RUN sed 's/^deb/deb-src/' /etc/apt/sources.list > \
/etc/apt/sources.list.d/deb-src.list \
&& dpkg --add-architecture {{ package_arch_name }} \
&& apt-get update \
&& apt-get install -y \
--no-install-recommends \
libssl-dev{{ package_arch_prefix }} \
libc6-dev{{ package_arch_prefix }}
{% endif -%}
{% if "aarch64" in target_file %}
RUN apt-get update \
&& apt-get install -y \
--no-install-recommends \
gcc-aarch64-linux-gnu \
&& mkdir -p ~/.cargo \
&& echo '[target.aarch64-unknown-linux-gnu]' >> ~/.cargo/config \
&& echo 'linker = "aarch64-linux-gnu-gcc"' >> ~/.cargo/config
ENV CARGO_HOME "/root/.cargo"
ENV USER "root"
{% elif "armv6" in target_file %}
RUN apt-get update \
&& apt-get install -y \
--no-install-recommends \
gcc-arm-linux-gnueabi \
&& mkdir -p ~/.cargo \
&& echo '[target.arm-unknown-linux-gnueabi]' >> ~/.cargo/config \
&& echo 'linker = "arm-linux-gnueabi-gcc"' >> ~/.cargo/config
ENV CARGO_HOME "/root/.cargo"
ENV USER "root"
{% elif "armv6" in target_file %}
RUN apt-get update \
&& apt-get install -y \
--no-install-recommends \
gcc-arm-linux-gnueabihf \
&& mkdir -p ~/.cargo \
&& echo '[target.armv7-unknown-linux-gnueabihf]' >> ~/.cargo/config \
&& echo 'linker = "arm-linux-gnueabihf-gcc"' >> ~/.cargo/config
ENV CARGO_HOME "/root/.cargo"
ENV USER "root"
{% elif "armv7" in target_file %}
RUN apt-get update \
&& apt-get install -y \
--no-install-recommends \
gcc-arm-linux-gnueabihf \
&& mkdir -p ~/.cargo \
&& echo '[target.armv7-unknown-linux-gnueabihf]' >> ~/.cargo/config \
&& echo 'linker = "arm-linux-gnueabihf-gcc"' >> ~/.cargo/config
ENV CARGO_HOME "/root/.cargo"
ENV USER "root"
{% endif %}
{% if "mysql" in target_file %}
# Install MySQL package
RUN apt-get update && apt-get install -y \
--no-install-recommends \
{% if "musl" in build_stage_base_image %}
libmysqlclient-dev{{ package_arch_prefix }} \
{% else %}
libmariadb-dev{{ package_arch_prefix }} \
{% endif %}
&& rm -rf /var/lib/apt/lists/*
{% elif "postgresql" in target_file %}
# Install PostgreSQL package
RUN apt-get update && apt-get install -y \
--no-install-recommends \
libpq-dev{{ package_arch_prefix }} \
&& rm -rf /var/lib/apt/lists/*
{% endif %}
# Creates a dummy project used to grab dependencies
RUN USER=root cargo new --bin /app
WORKDIR /app
# Copies over *only* your manifests and build files
COPY ./Cargo.* ./
COPY ./rust-toolchain ./rust-toolchain
COPY ./build.rs ./build.rs
{% if "aarch64" in target_file %}
ENV CC_aarch64_unknown_linux_gnu="/usr/bin/aarch64-linux-gnu-gcc"
ENV CROSS_COMPILE="1"
ENV OPENSSL_INCLUDE_DIR="/usr/include/aarch64-linux-gnu"
ENV OPENSSL_LIB_DIR="/usr/lib/aarch64-linux-gnu"
{% elif "armv6" in target_file %}
ENV CC_arm_unknown_linux_gnueabi="/usr/bin/arm-linux-gnueabi-gcc"
ENV CROSS_COMPILE="1"
ENV OPENSSL_INCLUDE_DIR="/usr/include/arm-linux-gnueabi"
ENV OPENSSL_LIB_DIR="/usr/lib/arm-linux-gnueabi"
{% elif "armv7" in target_file %}
ENV CC_armv7_unknown_linux_gnueabihf="/usr/bin/arm-linux-gnueabihf-gcc"
ENV CROSS_COMPILE="1"
ENV OPENSSL_INCLUDE_DIR="/usr/include/arm-linux-gnueabihf"
ENV OPENSSL_LIB_DIR="/usr/lib/arm-linux-gnueabihf"
{% endif -%}
{% if "alpine" in target_file %}
RUN rustup target add x86_64-unknown-linux-musl
{% elif "aarch64" in target_file %}
RUN rustup target add aarch64-unknown-linux-gnu
{% elif "armv6" in target_file %}
RUN rustup target add arm-unknown-linux-gnueabi
{% elif "armv7" in target_file %}
RUN rustup target add armv7-unknown-linux-gnueabihf
{% endif %}
# Builds your dependencies and removes the
# dummy project, except the target folder
# This folder contains the compiled dependencies
RUN cargo build --features ${DB} --release
RUN find . -not -path "./target*" -delete
# Copies the complete project
# To avoid copying unneeded files, use .dockerignore
COPY . .
# Make sure that we actually build the project
RUN touch src/main.rs
# Builds again, this time it'll just be
# your actual source files being built
{% if "amd64" in target_file %}
RUN cargo build --features ${DB} --release
{% elif "aarch64" in target_file %}
RUN cargo build --features ${DB} --release --target=aarch64-unknown-linux-gnu
{% elif "armv6" in target_file %}
RUN cargo build --features ${DB} --release --target=arm-unknown-linux-gnueabi
{% elif "armv7" in target_file %}
RUN cargo build --features ${DB} --release --target=armv7-unknown-linux-gnueabihf
{% endif %}
######################## RUNTIME IMAGE ########################
# Create a new stage with a minimal image
# because we already have a binary built
FROM {{ runtime_stage_base_image }}
ENV ROCKET_ENV "staging"
ENV ROCKET_PORT=80
ENV ROCKET_WORKERS=10
{% if "alpine" in runtime_stage_base_image %}
ENV SSL_CERT_DIR=/etc/ssl/certs
{% endif %}
{% if "amd64" not in target_file %}
RUN [ "cross-build-start" ]
{% endif %}
# Install needed libraries
{% if "alpine" in runtime_stage_base_image %}
RUN apk add --no-cache \
openssl \
curl \
{% if "sqlite" in target_file %}
sqlite \
{% elif "mysql" in target_file %}
mariadb-connector-c \
{% elif "postgresql" in target_file %}
postgresql-libs \
{% endif %}
ca-certificates
{% else %}
RUN apt-get update && apt-get install -y \
--no-install-recommends \
openssl \
ca-certificates \
curl \
{% if "sqlite" in target_file %}
sqlite3 \
{% elif "mysql" in target_file %}
libmariadbclient-dev \
{% elif "postgresql" in target_file %}
libpq5 \
{% endif %}
&& rm -rf /var/lib/apt/lists/*
{% endif %}
RUN mkdir /data
{% if "amd64" not in target_file %}
RUN [ "cross-build-end" ]
{% endif %}
VOLUME /data
EXPOSE 80
EXPOSE 3012
# Copies the files from the context (Rocket.toml file and web-vault)
# and the binary from the "build" stage to the current stage
COPY Rocket.toml .
COPY --from=vault /web-vault ./web-vault
{% if "alpine" in target_file %}
COPY --from=build /app/target/x86_64-unknown-linux-musl/release/bitwarden_rs .
{% elif "aarch64" in target_file %}
COPY --from=build /app/target/aarch64-unknown-linux-gnu/release/bitwarden_rs .
{% elif "armv6" in target_file %}
COPY --from=build /app/target/arm-unknown-linux-gnueabi/release/bitwarden_rs .
{% elif "armv7" in target_file %}
COPY --from=build /app/target/armv7-unknown-linux-gnueabihf/release/bitwarden_rs .
{% else %}
COPY --from=build app/target/release/bitwarden_rs .
{% endif %}
COPY docker/healthcheck.sh /healthcheck.sh
HEALTHCHECK --interval=60s --timeout=10s CMD ["/healthcheck.sh"]
# Configures the startup!
WORKDIR /
CMD ["/bitwarden_rs"]

9
docker/Makefile Normal file
View File

@@ -0,0 +1,9 @@
OBJECTS := $(shell find -mindepth 2 -name 'Dockerfile*')
all: $(OBJECTS)
%/Dockerfile: Dockerfile.j2 render_template
./render_template "$<" "{\"target_file\":\"$@\"}" > "$@"
%/Dockerfile.alpine: Dockerfile.j2 render_template
./render_template "$<" "{\"target_file\":\"$@\"}" > "$@"

View File

@@ -1,24 +1,21 @@
# This file was generated using a Jinja2 template.
# Please make your changes in `Dockerfile.j2` and then `make` the individual Dockerfile's.
# Using multistage build:
# https://docs.docker.com/develop/develop-images/multistage-build/
# https://whitfin.io/speeding-up-rust-docker-builds/
####################### VAULT BUILD IMAGE #######################
FROM alpine:3.11 as vault
ENV VAULT_VERSION "v2.12.0b"
ENV URL "https://github.com/dani-garcia/bw_web_builds/releases/download/$VAULT_VERSION/bw_web_$VAULT_VERSION.tar.gz"
RUN apk add --no-cache --upgrade \
curl \
tar
RUN mkdir /web-vault
WORKDIR /web-vault
SHELL ["/bin/ash", "-eo", "pipefail", "-c"]
RUN curl -L $URL | tar xz
RUN ls
# This hash is extracted from the docker web-vault builds and it's prefered over a simple tag because it's immutable.
# It can be viewed in multiple ways:
# - From the https://hub.docker.com/repository/docker/bitwardenrs/web-vault/tags page, click the tag name and the digest should be there.
# - From the console, with the following commands:
# docker pull bitwardenrs/web-vault:v2.13.2b
# docker image inspect --format "{{.RepoDigests}}" bitwardenrs/web-vault:v2.13.2b
#
# - To do the opposite, and get the tag from the hash, you can do:
# docker image inspect --format "{{.RepoTags}}" bitwardenrs/web-vault@sha256:f32c555a2bc3ee6bc0718319b1e8057c10ef889cf7231f0ff217af98486da554
FROM bitwardenrs/web-vault@sha256:f32c555a2bc3ee6bc0718319b1e8057c10ef889cf7231f0ff217af98486da554 as vault
########################## BUILD IMAGE ##########################
# We need to use the Rust build image, because
@@ -28,9 +25,22 @@ FROM rust:1.40 as build
# set mysql backend
ARG DB=mysql
# Build time options to avoid dpkg warnings and help with reproducible builds.
ENV DEBIAN_FRONTEND=noninteractive LANG=C.UTF-8 TZ=UTC TERM=xterm-256color
# Don't download rust docs
RUN rustup set profile minimal
# Install required build libs for arm64 architecture.
RUN sed 's/^deb/deb-src/' /etc/apt/sources.list > \
/etc/apt/sources.list.d/deb-src.list \
&& dpkg --add-architecture arm64 \
&& apt-get update \
&& apt-get install -y \
--no-install-recommends \
libssl-dev:arm64 \
libc6-dev:arm64
RUN apt-get update \
&& apt-get install -y \
--no-install-recommends \
@@ -42,30 +52,42 @@ RUN apt-get update \
ENV CARGO_HOME "/root/.cargo"
ENV USER "root"
# Install MySQL package
RUN apt-get update && apt-get install -y \
--no-install-recommends \
libmariadb-dev:arm64 \
&& rm -rf /var/lib/apt/lists/*
# Creates a dummy project used to grab dependencies
RUN USER=root cargo new --bin /app
WORKDIR /app
# Prepare openssl arm64 libs
RUN sed 's/^deb/deb-src/' /etc/apt/sources.list > \
/etc/apt/sources.list.d/deb-src.list \
&& dpkg --add-architecture arm64 \
&& apt-get update \
&& apt-get install -y \
--no-install-recommends \
libssl-dev:arm64 \
libc6-dev:arm64 \
libmariadb-dev:arm64
# Copies over *only* your manifests and build files
COPY ./Cargo.* ./
COPY ./rust-toolchain ./rust-toolchain
COPY ./build.rs ./build.rs
ENV CC_aarch64_unknown_linux_gnu="/usr/bin/aarch64-linux-gnu-gcc"
ENV CROSS_COMPILE="1"
ENV OPENSSL_INCLUDE_DIR="/usr/include/aarch64-linux-gnu"
ENV OPENSSL_LIB_DIR="/usr/lib/aarch64-linux-gnu"
RUN rustup target add aarch64-unknown-linux-gnu
# Builds your dependencies and removes the
# dummy project, except the target folder
# This folder contains the compiled dependencies
RUN cargo build --features ${DB} --release
RUN find . -not -path "./target*" -delete
# Copies the complete project
# To avoid copying unneeded files, use .dockerignore
COPY . .
# Build
RUN rustup target add aarch64-unknown-linux-gnu
# Make sure that we actually build the project
RUN touch src/main.rs
# Builds again, this time it'll just be
# your actual source files being built
RUN cargo build --features ${DB} --release --target=aarch64-unknown-linux-gnu
######################## RUNTIME IMAGE ########################
@@ -94,6 +116,7 @@ RUN [ "cross-build-end" ]
VOLUME /data
EXPOSE 80
EXPOSE 3012
# Copies the files from the context (Rocket.toml file and web-vault)
# and the binary from the "build" stage to the current stage
@@ -101,9 +124,9 @@ COPY Rocket.toml .
COPY --from=vault /web-vault ./web-vault
COPY --from=build /app/target/aarch64-unknown-linux-gnu/release/bitwarden_rs .
COPY docker/healthcheck.sh ./healthcheck.sh
COPY docker/healthcheck.sh /healthcheck.sh
HEALTHCHECK --interval=30s --timeout=3s CMD sh healthcheck.sh || exit 1
HEALTHCHECK --interval=60s --timeout=10s CMD ["/healthcheck.sh"]
# Configures the startup!
WORKDIR /

View File

@@ -1,36 +1,46 @@
# This file was generated using a Jinja2 template.
# Please make your changes in `Dockerfile.j2` and then `make` the individual Dockerfile's.
# Using multistage build:
# https://docs.docker.com/develop/develop-images/multistage-build/
# https://whitfin.io/speeding-up-rust-docker-builds/
####################### VAULT BUILD IMAGE #######################
FROM alpine:3.11 as vault
ENV VAULT_VERSION "v2.12.0b"
ENV URL "https://github.com/dani-garcia/bw_web_builds/releases/download/$VAULT_VERSION/bw_web_$VAULT_VERSION.tar.gz"
RUN apk add --no-cache --upgrade \
curl \
tar
RUN mkdir /web-vault
WORKDIR /web-vault
SHELL ["/bin/ash", "-eo", "pipefail", "-c"]
RUN curl -L $URL | tar xz
RUN ls
# This hash is extracted from the docker web-vault builds and it's prefered over a simple tag because it's immutable.
# It can be viewed in multiple ways:
# - From the https://hub.docker.com/repository/docker/bitwardenrs/web-vault/tags page, click the tag name and the digest should be there.
# - From the console, with the following commands:
# docker pull bitwardenrs/web-vault:v2.13.2b
# docker image inspect --format "{{.RepoDigests}}" bitwardenrs/web-vault:v2.13.2b
#
# - To do the opposite, and get the tag from the hash, you can do:
# docker image inspect --format "{{.RepoTags}}" bitwardenrs/web-vault@sha256:f32c555a2bc3ee6bc0718319b1e8057c10ef889cf7231f0ff217af98486da554
FROM bitwardenrs/web-vault@sha256:f32c555a2bc3ee6bc0718319b1e8057c10ef889cf7231f0ff217af98486da554 as vault
########################## BUILD IMAGE ##########################
# We need to use the Rust build image, because
# we need the Rust compiler and Cargo tooling
FROM rust:1.40 as build
# set sqlite as default for DB ARG for backward comaptibility
# set sqlite as default for DB ARG for backward compatibility
ARG DB=sqlite
# Build time options to avoid dpkg warnings and help with reproducible builds.
ENV DEBIAN_FRONTEND=noninteractive LANG=C.UTF-8 TZ=UTC TERM=xterm-256color
# Don't download rust docs
RUN rustup set profile minimal
# Install required build libs for arm64 architecture.
RUN sed 's/^deb/deb-src/' /etc/apt/sources.list > \
/etc/apt/sources.list.d/deb-src.list \
&& dpkg --add-architecture arm64 \
&& apt-get update \
&& apt-get install -y \
--no-install-recommends \
libssl-dev:arm64 \
libc6-dev:arm64
RUN apt-get update \
&& apt-get install -y \
--no-install-recommends \
@@ -42,29 +52,36 @@ RUN apt-get update \
ENV CARGO_HOME "/root/.cargo"
ENV USER "root"
# Creates a dummy project used to grab dependencies
RUN USER=root cargo new --bin /app
WORKDIR /app
# Prepare openssl arm64 libs
RUN sed 's/^deb/deb-src/' /etc/apt/sources.list > \
/etc/apt/sources.list.d/deb-src.list \
&& dpkg --add-architecture arm64 \
&& apt-get update \
&& apt-get install -y \
--no-install-recommends \
libssl-dev:arm64 \
libc6-dev:arm64
# Copies over *only* your manifests and build files
COPY ./Cargo.* ./
COPY ./rust-toolchain ./rust-toolchain
COPY ./build.rs ./build.rs
ENV CC_aarch64_unknown_linux_gnu="/usr/bin/aarch64-linux-gnu-gcc"
ENV CROSS_COMPILE="1"
ENV OPENSSL_INCLUDE_DIR="/usr/include/aarch64-linux-gnu"
ENV OPENSSL_LIB_DIR="/usr/lib/aarch64-linux-gnu"
RUN rustup target add aarch64-unknown-linux-gnu
# Builds your dependencies and removes the
# dummy project, except the target folder
# This folder contains the compiled dependencies
RUN cargo build --features ${DB} --release
RUN find . -not -path "./target*" -delete
# Copies the complete project
# To avoid copying unneeded files, use .dockerignore
COPY . .
# Build
RUN rustup target add aarch64-unknown-linux-gnu
# Make sure that we actually build the project
RUN touch src/main.rs
# Builds again, this time it'll just be
# your actual source files being built
RUN cargo build --features ${DB} --release --target=aarch64-unknown-linux-gnu
######################## RUNTIME IMAGE ########################
@@ -93,6 +110,7 @@ RUN [ "cross-build-end" ]
VOLUME /data
EXPOSE 80
EXPOSE 3012
# Copies the files from the context (Rocket.toml file and web-vault)
# and the binary from the "build" stage to the current stage
@@ -100,9 +118,9 @@ COPY Rocket.toml .
COPY --from=vault /web-vault ./web-vault
COPY --from=build /app/target/aarch64-unknown-linux-gnu/release/bitwarden_rs .
COPY docker/healthcheck.sh ./healthcheck.sh
COPY docker/healthcheck.sh /healthcheck.sh
HEALTHCHECK --interval=30s --timeout=3s CMD sh healthcheck.sh || exit 1
HEALTHCHECK --interval=60s --timeout=10s CMD ["/healthcheck.sh"]
# Configures the startup!
WORKDIR /

View File

@@ -1,24 +1,21 @@
# This file was generated using a Jinja2 template.
# Please make your changes in `Dockerfile.j2` and then `make` the individual Dockerfile's.
# Using multistage build:
# https://docs.docker.com/develop/develop-images/multistage-build/
# https://whitfin.io/speeding-up-rust-docker-builds/
####################### VAULT BUILD IMAGE #######################
FROM alpine:3.11 as vault
ENV VAULT_VERSION "v2.12.0b"
ENV URL "https://github.com/dani-garcia/bw_web_builds/releases/download/$VAULT_VERSION/bw_web_$VAULT_VERSION.tar.gz"
RUN apk add --no-cache --upgrade \
curl \
tar
RUN mkdir /web-vault
WORKDIR /web-vault
SHELL ["/bin/ash", "-eo", "pipefail", "-c"]
RUN curl -L $URL | tar xz
RUN ls
# This hash is extracted from the docker web-vault builds and it's prefered over a simple tag because it's immutable.
# It can be viewed in multiple ways:
# - From the https://hub.docker.com/repository/docker/bitwardenrs/web-vault/tags page, click the tag name and the digest should be there.
# - From the console, with the following commands:
# docker pull bitwardenrs/web-vault:v2.13.2b
# docker image inspect --format "{{.RepoDigests}}" bitwardenrs/web-vault:v2.13.2b
#
# - To do the opposite, and get the tag from the hash, you can do:
# docker image inspect --format "{{.RepoTags}}" bitwardenrs/web-vault@sha256:f32c555a2bc3ee6bc0718319b1e8057c10ef889cf7231f0ff217af98486da554
FROM bitwardenrs/web-vault@sha256:f32c555a2bc3ee6bc0718319b1e8057c10ef889cf7231f0ff217af98486da554 as vault
########################## BUILD IMAGE ##########################
# We need to use the Rust build image, because
@@ -28,6 +25,9 @@ FROM rust:1.40 as build
# set mysql backend
ARG DB=mysql
# Build time options to avoid dpkg warnings and help with reproducible builds.
ENV DEBIAN_FRONTEND=noninteractive LANG=C.UTF-8 TZ=UTC TERM=xterm-256color
# Don't download rust docs
RUN rustup set profile minimal
@@ -38,7 +38,7 @@ RUN apt-get update && apt-get install -y \
&& rm -rf /var/lib/apt/lists/*
# Creates a dummy project used to grab dependencies
RUN USER=root cargo new --bin app
RUN USER=root cargo new --bin /app
WORKDIR /app
# Copies over *only* your manifests and build files
@@ -92,9 +92,9 @@ COPY Rocket.toml .
COPY --from=vault /web-vault ./web-vault
COPY --from=build app/target/release/bitwarden_rs .
COPY docker/healthcheck.sh ./healthcheck.sh
COPY docker/healthcheck.sh /healthcheck.sh
HEALTHCHECK --interval=30s --timeout=3s CMD sh healthcheck.sh || exit 1
HEALTHCHECK --interval=60s --timeout=10s CMD ["/healthcheck.sh"]
# Configures the startup!
WORKDIR /

View File

@@ -1,55 +1,70 @@
# This file was generated using a Jinja2 template.
# Please make your changes in `Dockerfile.j2` and then `make` the individual Dockerfile's.
# Using multistage build:
# https://docs.docker.com/develop/develop-images/multistage-build/
# https://whitfin.io/speeding-up-rust-docker-builds/
####################### VAULT BUILD IMAGE #######################
FROM alpine:3.11 as vault
ENV VAULT_VERSION "v2.12.0b"
ENV URL "https://github.com/dani-garcia/bw_web_builds/releases/download/$VAULT_VERSION/bw_web_$VAULT_VERSION.tar.gz"
RUN apk add --no-cache --upgrade \
curl \
tar
RUN mkdir /web-vault
WORKDIR /web-vault
SHELL ["/bin/ash", "-eo", "pipefail", "-c"]
RUN curl -L $URL | tar xz
RUN ls
# This hash is extracted from the docker web-vault builds and it's prefered over a simple tag because it's immutable.
# It can be viewed in multiple ways:
# - From the https://hub.docker.com/repository/docker/bitwardenrs/web-vault/tags page, click the tag name and the digest should be there.
# - From the console, with the following commands:
# docker pull bitwardenrs/web-vault:v2.13.2b
# docker image inspect --format "{{.RepoDigests}}" bitwardenrs/web-vault:v2.13.2b
#
# - To do the opposite, and get the tag from the hash, you can do:
# docker image inspect --format "{{.RepoTags}}" bitwardenrs/web-vault@sha256:f32c555a2bc3ee6bc0718319b1e8057c10ef889cf7231f0ff217af98486da554
FROM bitwardenrs/web-vault@sha256:f32c555a2bc3ee6bc0718319b1e8057c10ef889cf7231f0ff217af98486da554 as vault
########################## BUILD IMAGE ##########################
# Musl build image for statically compiled binary
FROM clux/muslrust:nightly-2019-12-19 as build
FROM clux/muslrust:nightly-2020-03-09 as build
# set mysql backend
ARG DB=mysql
# Build time options to avoid dpkg warnings and help with reproducible builds.
ENV DEBIAN_FRONTEND=noninteractive LANG=C.UTF-8 TZ=UTC TERM=xterm-256color
# Don't download rust docs
RUN rustup set profile minimal
ENV USER "root"
ENV RUSTFLAGS='-C link-arg=-s'
# Install needed libraries
# Install MySQL package
RUN apt-get update && apt-get install -y \
--no-install-recommends \
libmysqlclient-dev \
&& rm -rf /var/lib/apt/lists/*
# Creates a dummy project used to grab dependencies
RUN USER=root cargo new --bin /app
WORKDIR /app
# Copies over *only* your manifests and build files
COPY ./Cargo.* ./
COPY ./rust-toolchain ./rust-toolchain
COPY ./build.rs ./build.rs
RUN rustup target add x86_64-unknown-linux-musl
# Builds your dependencies and removes the
# dummy project, except the target folder
# This folder contains the compiled dependencies
RUN cargo build --features ${DB} --release
RUN find . -not -path "./target*" -delete
# Copies the complete project
# To avoid copying unneeded files, use .dockerignore
COPY . .
RUN rustup target add x86_64-unknown-linux-musl
# Make sure that we actually build the project
RUN touch src/main.rs
# Build
# Builds again, this time it'll just be
# your actual source files being built
RUN cargo build --features ${DB} --release
######################## RUNTIME IMAGE ########################
@@ -65,8 +80,8 @@ ENV SSL_CERT_DIR=/etc/ssl/certs
# Install needed libraries
RUN apk add --no-cache \
openssl \
mariadb-connector-c \
curl \
mariadb-connector-c \
ca-certificates
RUN mkdir /data
@@ -80,9 +95,9 @@ COPY Rocket.toml .
COPY --from=vault /web-vault ./web-vault
COPY --from=build /app/target/x86_64-unknown-linux-musl/release/bitwarden_rs .
COPY docker/healthcheck.sh ./healthcheck.sh
COPY docker/healthcheck.sh /healthcheck.sh
HEALTHCHECK --interval=30s --timeout=3s CMD sh healthcheck.sh || exit 1
HEALTHCHECK --interval=60s --timeout=10s CMD ["/healthcheck.sh"]
# Configures the startup!
WORKDIR /

View File

@@ -1,50 +1,44 @@
# This file was generated using a Jinja2 template.
# Please make your changes in `Dockerfile.j2` and then `make` the individual Dockerfile's.
# Using multistage build:
# https://docs.docker.com/develop/develop-images/multistage-build/
# https://whitfin.io/speeding-up-rust-docker-builds/
####################### VAULT BUILD IMAGE #######################
FROM alpine:3.11 as vault
ENV VAULT_VERSION "v2.12.0b"
ENV URL "https://github.com/dani-garcia/bw_web_builds/releases/download/$VAULT_VERSION/bw_web_$VAULT_VERSION.tar.gz"
RUN apk add --no-cache --upgrade \
curl \
tar
RUN mkdir /web-vault
WORKDIR /web-vault
SHELL ["/bin/ash", "-eo", "pipefail", "-c"]
RUN curl -L $URL | tar xz
RUN ls
# This hash is extracted from the docker web-vault builds and it's prefered over a simple tag because it's immutable.
# It can be viewed in multiple ways:
# - From the https://hub.docker.com/repository/docker/bitwardenrs/web-vault/tags page, click the tag name and the digest should be there.
# - From the console, with the following commands:
# docker pull bitwardenrs/web-vault:v2.13.2b
# docker image inspect --format "{{.RepoDigests}}" bitwardenrs/web-vault:v2.13.2b
#
# - To do the opposite, and get the tag from the hash, you can do:
# docker image inspect --format "{{.RepoTags}}" bitwardenrs/web-vault@sha256:f32c555a2bc3ee6bc0718319b1e8057c10ef889cf7231f0ff217af98486da554
FROM bitwardenrs/web-vault@sha256:f32c555a2bc3ee6bc0718319b1e8057c10ef889cf7231f0ff217af98486da554 as vault
########################## BUILD IMAGE ##########################
# We need to use the Rust build image, because
# we need the Rust compiler and Cargo tooling
FROM rust:1.40 as build
# set mysql backend
# set postgresql backend
ARG DB=postgresql
# Build time options to avoid dpkg warnings and help with reproducible builds.
ENV DEBIAN_FRONTEND=noninteractive LANG=C.UTF-8 TZ=UTC TERM=xterm-256color
# Don't download rust docs
RUN rustup set profile minimal
# Using bundled SQLite, no need to install it
# RUN apt-get update && apt-get install -y\
# --no-install-recommends \
# sqlite3\
# && rm -rf /var/lib/apt/lists/*
# Install MySQL package
# Install PostgreSQL package
RUN apt-get update && apt-get install -y \
--no-install-recommends \
libpq-dev \
&& rm -rf /var/lib/apt/lists/*
# Creates a dummy project used to grab dependencies
RUN USER=root cargo new --bin app
RUN USER=root cargo new --bin /app
WORKDIR /app
# Copies over *only* your manifests and build files
@@ -84,7 +78,6 @@ RUN apt-get update && apt-get install -y \
openssl \
ca-certificates \
curl \
sqlite3 \
libpq5 \
&& rm -rf /var/lib/apt/lists/*
@@ -99,9 +92,9 @@ COPY Rocket.toml .
COPY --from=vault /web-vault ./web-vault
COPY --from=build app/target/release/bitwarden_rs .
COPY docker/healthcheck.sh ./healthcheck.sh
COPY docker/healthcheck.sh /healthcheck.sh
HEALTHCHECK --interval=30s --timeout=3s CMD sh healthcheck.sh || exit 1
HEALTHCHECK --interval=60s --timeout=10s CMD ["/healthcheck.sh"]
# Configures the startup!
WORKDIR /

View File

@@ -1,55 +1,70 @@
# This file was generated using a Jinja2 template.
# Please make your changes in `Dockerfile.j2` and then `make` the individual Dockerfile's.
# Using multistage build:
# https://docs.docker.com/develop/develop-images/multistage-build/
# https://whitfin.io/speeding-up-rust-docker-builds/
####################### VAULT BUILD IMAGE #######################
FROM alpine:3.11 as vault
ENV VAULT_VERSION "v2.12.0b"
ENV URL "https://github.com/dani-garcia/bw_web_builds/releases/download/$VAULT_VERSION/bw_web_$VAULT_VERSION.tar.gz"
RUN apk add --no-cache --upgrade \
curl \
tar
RUN mkdir /web-vault
WORKDIR /web-vault
SHELL ["/bin/ash", "-eo", "pipefail", "-c"]
RUN curl -L $URL | tar xz
RUN ls
# This hash is extracted from the docker web-vault builds and it's prefered over a simple tag because it's immutable.
# It can be viewed in multiple ways:
# - From the https://hub.docker.com/repository/docker/bitwardenrs/web-vault/tags page, click the tag name and the digest should be there.
# - From the console, with the following commands:
# docker pull bitwardenrs/web-vault:v2.13.2b
# docker image inspect --format "{{.RepoDigests}}" bitwardenrs/web-vault:v2.13.2b
#
# - To do the opposite, and get the tag from the hash, you can do:
# docker image inspect --format "{{.RepoTags}}" bitwardenrs/web-vault@sha256:f32c555a2bc3ee6bc0718319b1e8057c10ef889cf7231f0ff217af98486da554
FROM bitwardenrs/web-vault@sha256:f32c555a2bc3ee6bc0718319b1e8057c10ef889cf7231f0ff217af98486da554 as vault
########################## BUILD IMAGE ##########################
# Musl build image for statically compiled binary
FROM clux/muslrust:nightly-2019-12-19 as build
FROM clux/muslrust:nightly-2020-03-09 as build
# set postgresql backend
ARG DB=postgresql
# Build time options to avoid dpkg warnings and help with reproducible builds.
ENV DEBIAN_FRONTEND=noninteractive LANG=C.UTF-8 TZ=UTC TERM=xterm-256color
# Don't download rust docs
RUN rustup set profile minimal
ENV USER "root"
ENV RUSTFLAGS='-C link-arg=-s'
# Install needed libraries
# Install PostgreSQL package
RUN apt-get update && apt-get install -y \
--no-install-recommends \
libpq-dev \
&& rm -rf /var/lib/apt/lists/*
# Creates a dummy project used to grab dependencies
RUN USER=root cargo new --bin /app
WORKDIR /app
# Copies over *only* your manifests and build files
COPY ./Cargo.* ./
COPY ./rust-toolchain ./rust-toolchain
COPY ./build.rs ./build.rs
RUN rustup target add x86_64-unknown-linux-musl
# Builds your dependencies and removes the
# dummy project, except the target folder
# This folder contains the compiled dependencies
RUN cargo build --features ${DB} --release
RUN find . -not -path "./target*" -delete
# Copies the complete project
# To avoid copying unneeded files, use .dockerignore
COPY . .
RUN rustup target add x86_64-unknown-linux-musl
# Make sure that we actually build the project
RUN touch src/main.rs
# Build
# Builds again, this time it'll just be
# your actual source files being built
RUN cargo build --features ${DB} --release
######################## RUNTIME IMAGE ########################
@@ -65,9 +80,8 @@ ENV SSL_CERT_DIR=/etc/ssl/certs
# Install needed libraries
RUN apk add --no-cache \
openssl \
postgresql-libs \
curl \
sqlite \
postgresql-libs \
ca-certificates
RUN mkdir /data
@@ -81,9 +95,9 @@ COPY Rocket.toml .
COPY --from=vault /web-vault ./web-vault
COPY --from=build /app/target/x86_64-unknown-linux-musl/release/bitwarden_rs .
COPY docker/healthcheck.sh ./healthcheck.sh
COPY docker/healthcheck.sh /healthcheck.sh
HEALTHCHECK --interval=30s --timeout=3s CMD sh healthcheck.sh || exit 1
HEALTHCHECK --interval=60s --timeout=10s CMD ["/healthcheck.sh"]
# Configures the startup!
WORKDIR /

View File

@@ -1,38 +1,38 @@
# This file was generated using a Jinja2 template.
# Please make your changes in `Dockerfile.j2` and then `make` the individual Dockerfile's.
# Using multistage build:
# https://docs.docker.com/develop/develop-images/multistage-build/
# https://whitfin.io/speeding-up-rust-docker-builds/
####################### VAULT BUILD IMAGE #######################
FROM alpine:3.11 as vault
ENV VAULT_VERSION "v2.12.0b"
ENV URL "https://github.com/dani-garcia/bw_web_builds/releases/download/$VAULT_VERSION/bw_web_$VAULT_VERSION.tar.gz"
RUN apk add --no-cache --upgrade \
curl \
tar
RUN mkdir /web-vault
WORKDIR /web-vault
SHELL ["/bin/ash", "-eo", "pipefail", "-c"]
RUN curl -L $URL | tar xz
RUN ls
# This hash is extracted from the docker web-vault builds and it's prefered over a simple tag because it's immutable.
# It can be viewed in multiple ways:
# - From the https://hub.docker.com/repository/docker/bitwardenrs/web-vault/tags page, click the tag name and the digest should be there.
# - From the console, with the following commands:
# docker pull bitwardenrs/web-vault:v2.13.2b
# docker image inspect --format "{{.RepoDigests}}" bitwardenrs/web-vault:v2.13.2b
#
# - To do the opposite, and get the tag from the hash, you can do:
# docker image inspect --format "{{.RepoTags}}" bitwardenrs/web-vault@sha256:f32c555a2bc3ee6bc0718319b1e8057c10ef889cf7231f0ff217af98486da554
FROM bitwardenrs/web-vault@sha256:f32c555a2bc3ee6bc0718319b1e8057c10ef889cf7231f0ff217af98486da554 as vault
########################## BUILD IMAGE ##########################
# We need to use the Rust build image, because
# we need the Rust compiler and Cargo tooling
FROM rust:1.40 as build
# set sqlite as default for DB ARG for backward comaptibility
# set sqlite as default for DB ARG for backward compatibility
ARG DB=sqlite
# Build time options to avoid dpkg warnings and help with reproducible builds.
ENV DEBIAN_FRONTEND=noninteractive LANG=C.UTF-8 TZ=UTC TERM=xterm-256color
# Don't download rust docs
RUN rustup set profile minimal
# Creates a dummy project used to grab dependencies
RUN USER=root cargo new --bin app
RUN USER=root cargo new --bin /app
WORKDIR /app
# Copies over *only* your manifests and build files
@@ -86,9 +86,9 @@ COPY Rocket.toml .
COPY --from=vault /web-vault ./web-vault
COPY --from=build app/target/release/bitwarden_rs .
COPY docker/healthcheck.sh ./healthcheck.sh
COPY docker/healthcheck.sh /healthcheck.sh
HEALTHCHECK --interval=30s --timeout=3s CMD sh healthcheck.sh || exit 1
HEALTHCHECK --interval=60s --timeout=10s CMD ["/healthcheck.sh"]
# Configures the startup!
WORKDIR /

View File

@@ -1,49 +1,64 @@
# This file was generated using a Jinja2 template.
# Please make your changes in `Dockerfile.j2` and then `make` the individual Dockerfile's.
# Using multistage build:
# https://docs.docker.com/develop/develop-images/multistage-build/
# https://whitfin.io/speeding-up-rust-docker-builds/
####################### VAULT BUILD IMAGE #######################
FROM alpine:3.11 as vault
ENV VAULT_VERSION "v2.12.0b"
ENV URL "https://github.com/dani-garcia/bw_web_builds/releases/download/$VAULT_VERSION/bw_web_$VAULT_VERSION.tar.gz"
RUN apk add --no-cache --upgrade \
curl \
tar
RUN mkdir /web-vault
WORKDIR /web-vault
SHELL ["/bin/ash", "-eo", "pipefail", "-c"]
RUN curl -L $URL | tar xz
RUN ls
# This hash is extracted from the docker web-vault builds and it's prefered over a simple tag because it's immutable.
# It can be viewed in multiple ways:
# - From the https://hub.docker.com/repository/docker/bitwardenrs/web-vault/tags page, click the tag name and the digest should be there.
# - From the console, with the following commands:
# docker pull bitwardenrs/web-vault:v2.13.2b
# docker image inspect --format "{{.RepoDigests}}" bitwardenrs/web-vault:v2.13.2b
#
# - To do the opposite, and get the tag from the hash, you can do:
# docker image inspect --format "{{.RepoTags}}" bitwardenrs/web-vault@sha256:f32c555a2bc3ee6bc0718319b1e8057c10ef889cf7231f0ff217af98486da554
FROM bitwardenrs/web-vault@sha256:f32c555a2bc3ee6bc0718319b1e8057c10ef889cf7231f0ff217af98486da554 as vault
########################## BUILD IMAGE ##########################
# Musl build image for statically compiled binary
FROM clux/muslrust:nightly-2019-12-19 as build
FROM clux/muslrust:nightly-2020-03-09 as build
# set sqlite as default for DB ARG for backward comaptibility
# set sqlite as default for DB ARG for backward compatibility
ARG DB=sqlite
# Build time options to avoid dpkg warnings and help with reproducible builds.
ENV DEBIAN_FRONTEND=noninteractive LANG=C.UTF-8 TZ=UTC TERM=xterm-256color
# Don't download rust docs
RUN rustup set profile minimal
ENV USER "root"
ENV RUSTFLAGS='-C link-arg=-s'
# Creates a dummy project used to grab dependencies
RUN USER=root cargo new --bin /app
WORKDIR /app
# Copies over *only* your manifests and build files
COPY ./Cargo.* ./
COPY ./rust-toolchain ./rust-toolchain
COPY ./build.rs ./build.rs
RUN rustup target add x86_64-unknown-linux-musl
# Builds your dependencies and removes the
# dummy project, except the target folder
# This folder contains the compiled dependencies
RUN cargo build --features ${DB} --release
RUN find . -not -path "./target*" -delete
# Copies the complete project
# To avoid copying unneeded files, use .dockerignore
COPY . .
RUN rustup target add x86_64-unknown-linux-musl
# Make sure that we actually build the project
RUN touch src/main.rs
# Build
# Builds again, this time it'll just be
# your actual source files being built
RUN cargo build --features ${DB} --release
######################## RUNTIME IMAGE ########################
@@ -74,10 +89,9 @@ COPY Rocket.toml .
COPY --from=vault /web-vault ./web-vault
COPY --from=build /app/target/x86_64-unknown-linux-musl/release/bitwarden_rs .
COPY docker/healthcheck.sh ./healthcheck.sh
HEALTHCHECK --interval=30s --timeout=3s CMD sh healthcheck.sh || exit 1
COPY docker/healthcheck.sh /healthcheck.sh
HEALTHCHECK --interval=60s --timeout=10s CMD ["/healthcheck.sh"]
# Configures the startup!
WORKDIR /

View File

@@ -1,24 +1,21 @@
# This file was generated using a Jinja2 template.
# Please make your changes in `Dockerfile.j2` and then `make` the individual Dockerfile's.
# Using multistage build:
# https://docs.docker.com/develop/develop-images/multistage-build/
# https://whitfin.io/speeding-up-rust-docker-builds/
####################### VAULT BUILD IMAGE #######################
FROM alpine:3.11 as vault
ENV VAULT_VERSION "v2.12.0b"
ENV URL "https://github.com/dani-garcia/bw_web_builds/releases/download/$VAULT_VERSION/bw_web_$VAULT_VERSION.tar.gz"
RUN apk add --no-cache --upgrade \
curl \
tar
RUN mkdir /web-vault
WORKDIR /web-vault
SHELL ["/bin/ash", "-eo", "pipefail", "-c"]
RUN curl -L $URL | tar xz
RUN ls
# This hash is extracted from the docker web-vault builds and it's prefered over a simple tag because it's immutable.
# It can be viewed in multiple ways:
# - From the https://hub.docker.com/repository/docker/bitwardenrs/web-vault/tags page, click the tag name and the digest should be there.
# - From the console, with the following commands:
# docker pull bitwardenrs/web-vault:v2.13.2b
# docker image inspect --format "{{.RepoDigests}}" bitwardenrs/web-vault:v2.13.2b
#
# - To do the opposite, and get the tag from the hash, you can do:
# docker image inspect --format "{{.RepoTags}}" bitwardenrs/web-vault@sha256:f32c555a2bc3ee6bc0718319b1e8057c10ef889cf7231f0ff217af98486da554
FROM bitwardenrs/web-vault@sha256:f32c555a2bc3ee6bc0718319b1e8057c10ef889cf7231f0ff217af98486da554 as vault
########################## BUILD IMAGE ##########################
# We need to use the Rust build image, because
@@ -28,9 +25,22 @@ FROM rust:1.40 as build
# set mysql backend
ARG DB=mysql
# Build time options to avoid dpkg warnings and help with reproducible builds.
ENV DEBIAN_FRONTEND=noninteractive LANG=C.UTF-8 TZ=UTC TERM=xterm-256color
# Don't download rust docs
RUN rustup set profile minimal
# Install required build libs for armel architecture.
RUN sed 's/^deb/deb-src/' /etc/apt/sources.list > \
/etc/apt/sources.list.d/deb-src.list \
&& dpkg --add-architecture armel \
&& apt-get update \
&& apt-get install -y \
--no-install-recommends \
libssl-dev:armel \
libc6-dev:armel
RUN apt-get update \
&& apt-get install -y \
--no-install-recommends \
@@ -42,30 +52,42 @@ RUN apt-get update \
ENV CARGO_HOME "/root/.cargo"
ENV USER "root"
# Install MySQL package
RUN apt-get update && apt-get install -y \
--no-install-recommends \
libmariadb-dev:armel \
&& rm -rf /var/lib/apt/lists/*
# Creates a dummy project used to grab dependencies
RUN USER=root cargo new --bin /app
WORKDIR /app
# Prepare openssl armel libs
RUN sed 's/^deb/deb-src/' /etc/apt/sources.list > \
/etc/apt/sources.list.d/deb-src.list \
&& dpkg --add-architecture armel \
&& apt-get update \
&& apt-get install -y \
--no-install-recommends \
libssl-dev:armel \
libc6-dev:armel \
libmariadb-dev:armel
# Copies over *only* your manifests and build files
COPY ./Cargo.* ./
COPY ./rust-toolchain ./rust-toolchain
COPY ./build.rs ./build.rs
ENV CC_arm_unknown_linux_gnueabi="/usr/bin/arm-linux-gnueabi-gcc"
ENV CROSS_COMPILE="1"
ENV OPENSSL_INCLUDE_DIR="/usr/include/arm-linux-gnueabi"
ENV OPENSSL_LIB_DIR="/usr/lib/arm-linux-gnueabi"
RUN rustup target add arm-unknown-linux-gnueabi
# Builds your dependencies and removes the
# dummy project, except the target folder
# This folder contains the compiled dependencies
RUN cargo build --features ${DB} --release
RUN find . -not -path "./target*" -delete
# Copies the complete project
# To avoid copying unneeded files, use .dockerignore
COPY . .
# Build
RUN rustup target add arm-unknown-linux-gnueabi
# Make sure that we actually build the project
RUN touch src/main.rs
# Builds again, this time it'll just be
# your actual source files being built
RUN cargo build --features ${DB} --release --target=arm-unknown-linux-gnueabi
######################## RUNTIME IMAGE ########################
@@ -94,6 +116,7 @@ RUN [ "cross-build-end" ]
VOLUME /data
EXPOSE 80
EXPOSE 3012
# Copies the files from the context (Rocket.toml file and web-vault)
# and the binary from the "build" stage to the current stage
@@ -101,9 +124,9 @@ COPY Rocket.toml .
COPY --from=vault /web-vault ./web-vault
COPY --from=build /app/target/arm-unknown-linux-gnueabi/release/bitwarden_rs .
COPY docker/healthcheck.sh ./healthcheck.sh
COPY docker/healthcheck.sh /healthcheck.sh
HEALTHCHECK --interval=30s --timeout=3s CMD sh healthcheck.sh || exit 1
HEALTHCHECK --interval=60s --timeout=10s CMD ["/healthcheck.sh"]
# Configures the startup!
WORKDIR /

View File

@@ -1,36 +1,46 @@
# This file was generated using a Jinja2 template.
# Please make your changes in `Dockerfile.j2` and then `make` the individual Dockerfile's.
# Using multistage build:
# https://docs.docker.com/develop/develop-images/multistage-build/
# https://whitfin.io/speeding-up-rust-docker-builds/
####################### VAULT BUILD IMAGE #######################
FROM alpine:3.11 as vault
ENV VAULT_VERSION "v2.12.0b"
ENV URL "https://github.com/dani-garcia/bw_web_builds/releases/download/$VAULT_VERSION/bw_web_$VAULT_VERSION.tar.gz"
RUN apk add --no-cache --upgrade \
curl \
tar
RUN mkdir /web-vault
WORKDIR /web-vault
SHELL ["/bin/ash", "-eo", "pipefail", "-c"]
RUN curl -L $URL | tar xz
RUN ls
# This hash is extracted from the docker web-vault builds and it's prefered over a simple tag because it's immutable.
# It can be viewed in multiple ways:
# - From the https://hub.docker.com/repository/docker/bitwardenrs/web-vault/tags page, click the tag name and the digest should be there.
# - From the console, with the following commands:
# docker pull bitwardenrs/web-vault:v2.13.2b
# docker image inspect --format "{{.RepoDigests}}" bitwardenrs/web-vault:v2.13.2b
#
# - To do the opposite, and get the tag from the hash, you can do:
# docker image inspect --format "{{.RepoTags}}" bitwardenrs/web-vault@sha256:f32c555a2bc3ee6bc0718319b1e8057c10ef889cf7231f0ff217af98486da554
FROM bitwardenrs/web-vault@sha256:f32c555a2bc3ee6bc0718319b1e8057c10ef889cf7231f0ff217af98486da554 as vault
########################## BUILD IMAGE ##########################
# We need to use the Rust build image, because
# we need the Rust compiler and Cargo tooling
FROM rust:1.40 as build
# set sqlite as default for DB ARG for backward comaptibility
# set sqlite as default for DB ARG for backward compatibility
ARG DB=sqlite
# Build time options to avoid dpkg warnings and help with reproducible builds.
ENV DEBIAN_FRONTEND=noninteractive LANG=C.UTF-8 TZ=UTC TERM=xterm-256color
# Don't download rust docs
RUN rustup set profile minimal
# Install required build libs for armel architecture.
RUN sed 's/^deb/deb-src/' /etc/apt/sources.list > \
/etc/apt/sources.list.d/deb-src.list \
&& dpkg --add-architecture armel \
&& apt-get update \
&& apt-get install -y \
--no-install-recommends \
libssl-dev:armel \
libc6-dev:armel
RUN apt-get update \
&& apt-get install -y \
--no-install-recommends \
@@ -42,29 +52,36 @@ RUN apt-get update \
ENV CARGO_HOME "/root/.cargo"
ENV USER "root"
# Creates a dummy project used to grab dependencies
RUN USER=root cargo new --bin /app
WORKDIR /app
# Prepare openssl armel libs
RUN sed 's/^deb/deb-src/' /etc/apt/sources.list > \
/etc/apt/sources.list.d/deb-src.list \
&& dpkg --add-architecture armel \
&& apt-get update \
&& apt-get install -y \
--no-install-recommends \
libssl-dev:armel \
libc6-dev:armel
# Copies over *only* your manifests and build files
COPY ./Cargo.* ./
COPY ./rust-toolchain ./rust-toolchain
COPY ./build.rs ./build.rs
ENV CC_arm_unknown_linux_gnueabi="/usr/bin/arm-linux-gnueabi-gcc"
ENV CROSS_COMPILE="1"
ENV OPENSSL_INCLUDE_DIR="/usr/include/arm-linux-gnueabi"
ENV OPENSSL_LIB_DIR="/usr/lib/arm-linux-gnueabi"
RUN rustup target add arm-unknown-linux-gnueabi
# Builds your dependencies and removes the
# dummy project, except the target folder
# This folder contains the compiled dependencies
RUN cargo build --features ${DB} --release
RUN find . -not -path "./target*" -delete
# Copies the complete project
# To avoid copying unneeded files, use .dockerignore
COPY . .
# Build
RUN rustup target add arm-unknown-linux-gnueabi
# Make sure that we actually build the project
RUN touch src/main.rs
# Builds again, this time it'll just be
# your actual source files being built
RUN cargo build --features ${DB} --release --target=arm-unknown-linux-gnueabi
######################## RUNTIME IMAGE ########################
@@ -93,6 +110,7 @@ RUN [ "cross-build-end" ]
VOLUME /data
EXPOSE 80
EXPOSE 3012
# Copies the files from the context (Rocket.toml file and web-vault)
# and the binary from the "build" stage to the current stage
@@ -100,9 +118,9 @@ COPY Rocket.toml .
COPY --from=vault /web-vault ./web-vault
COPY --from=build /app/target/arm-unknown-linux-gnueabi/release/bitwarden_rs .
COPY docker/healthcheck.sh ./healthcheck.sh
COPY docker/healthcheck.sh /healthcheck.sh
HEALTHCHECK --interval=30s --timeout=3s CMD sh healthcheck.sh || exit 1
HEALTHCHECK --interval=60s --timeout=10s CMD ["/healthcheck.sh"]
# Configures the startup!
WORKDIR /

View File

@@ -1,24 +1,21 @@
# This file was generated using a Jinja2 template.
# Please make your changes in `Dockerfile.j2` and then `make` the individual Dockerfile's.
# Using multistage build:
# https://docs.docker.com/develop/develop-images/multistage-build/
# https://whitfin.io/speeding-up-rust-docker-builds/
####################### VAULT BUILD IMAGE #######################
FROM alpine:3.11 as vault
ENV VAULT_VERSION "v2.12.0b"
ENV URL "https://github.com/dani-garcia/bw_web_builds/releases/download/$VAULT_VERSION/bw_web_$VAULT_VERSION.tar.gz"
RUN apk add --no-cache --upgrade \
curl \
tar
RUN mkdir /web-vault
WORKDIR /web-vault
SHELL ["/bin/ash", "-eo", "pipefail", "-c"]
RUN curl -L $URL | tar xz
RUN ls
# This hash is extracted from the docker web-vault builds and it's prefered over a simple tag because it's immutable.
# It can be viewed in multiple ways:
# - From the https://hub.docker.com/repository/docker/bitwardenrs/web-vault/tags page, click the tag name and the digest should be there.
# - From the console, with the following commands:
# docker pull bitwardenrs/web-vault:v2.13.2b
# docker image inspect --format "{{.RepoDigests}}" bitwardenrs/web-vault:v2.13.2b
#
# - To do the opposite, and get the tag from the hash, you can do:
# docker image inspect --format "{{.RepoTags}}" bitwardenrs/web-vault@sha256:f32c555a2bc3ee6bc0718319b1e8057c10ef889cf7231f0ff217af98486da554
FROM bitwardenrs/web-vault@sha256:f32c555a2bc3ee6bc0718319b1e8057c10ef889cf7231f0ff217af98486da554 as vault
########################## BUILD IMAGE ##########################
# We need to use the Rust build image, because
@@ -28,9 +25,22 @@ FROM rust:1.40 as build
# set mysql backend
ARG DB=mysql
# Build time options to avoid dpkg warnings and help with reproducible builds.
ENV DEBIAN_FRONTEND=noninteractive LANG=C.UTF-8 TZ=UTC TERM=xterm-256color
# Don't download rust docs
RUN rustup set profile minimal
# Install required build libs for armhf architecture.
RUN sed 's/^deb/deb-src/' /etc/apt/sources.list > \
/etc/apt/sources.list.d/deb-src.list \
&& dpkg --add-architecture armhf \
&& apt-get update \
&& apt-get install -y \
--no-install-recommends \
libssl-dev:armhf \
libc6-dev:armhf
RUN apt-get update \
&& apt-get install -y \
--no-install-recommends \
@@ -42,31 +52,41 @@ RUN apt-get update \
ENV CARGO_HOME "/root/.cargo"
ENV USER "root"
# Install MySQL package
RUN apt-get update && apt-get install -y \
--no-install-recommends \
libmariadb-dev:armhf \
&& rm -rf /var/lib/apt/lists/*
# Creates a dummy project used to grab dependencies
RUN USER=root cargo new --bin /app
WORKDIR /app
# Prepare openssl armhf libs
RUN sed 's/^deb/deb-src/' /etc/apt/sources.list > \
/etc/apt/sources.list.d/deb-src.list \
&& dpkg --add-architecture armhf \
&& apt-get update \
&& apt-get install -y \
--no-install-recommends \
libssl-dev:armhf \
libc6-dev:armhf \
libmariadb-dev:armhf
# Copies over *only* your manifests and build files
COPY ./Cargo.* ./
COPY ./rust-toolchain ./rust-toolchain
COPY ./build.rs ./build.rs
ENV CC_armv7_unknown_linux_gnueabihf="/usr/bin/arm-linux-gnueabihf-gcc"
ENV CROSS_COMPILE="1"
ENV OPENSSL_INCLUDE_DIR="/usr/include/arm-linux-gnueabihf"
ENV OPENSSL_LIB_DIR="/usr/lib/arm-linux-gnueabihf"
RUN rustup target add armv7-unknown-linux-gnueabihf
# Builds your dependencies and removes the
# dummy project, except the target folder
# This folder contains the compiled dependencies
RUN cargo build --features ${DB} --release
RUN find . -not -path "./target*" -delete
# Copies the complete project
# To avoid copying unneeded files, use .dockerignore
COPY . .
# Build
RUN rustup target add armv7-unknown-linux-gnueabihf
# Make sure that we actually build the project
RUN touch src/main.rs
# Builds again, this time it'll just be
# your actual source files being built
RUN cargo build --features ${DB} --release --target=armv7-unknown-linux-gnueabihf
######################## RUNTIME IMAGE ########################
@@ -95,6 +115,7 @@ RUN [ "cross-build-end" ]
VOLUME /data
EXPOSE 80
EXPOSE 3012
# Copies the files from the context (Rocket.toml file and web-vault)
# and the binary from the "build" stage to the current stage
@@ -102,9 +123,9 @@ COPY Rocket.toml .
COPY --from=vault /web-vault ./web-vault
COPY --from=build /app/target/armv7-unknown-linux-gnueabihf/release/bitwarden_rs .
COPY docker/healthcheck.sh ./healthcheck.sh
COPY docker/healthcheck.sh /healthcheck.sh
HEALTHCHECK --interval=30s --timeout=3s CMD sh healthcheck.sh || exit 1
HEALTHCHECK --interval=60s --timeout=10s CMD ["/healthcheck.sh"]
# Configures the startup!
WORKDIR /

View File

@@ -1,36 +1,46 @@
# This file was generated using a Jinja2 template.
# Please make your changes in `Dockerfile.j2` and then `make` the individual Dockerfile's.
# Using multistage build:
# https://docs.docker.com/develop/develop-images/multistage-build/
# https://whitfin.io/speeding-up-rust-docker-builds/
####################### VAULT BUILD IMAGE #######################
FROM alpine:3.11 as vault
ENV VAULT_VERSION "v2.12.0b"
ENV URL "https://github.com/dani-garcia/bw_web_builds/releases/download/$VAULT_VERSION/bw_web_$VAULT_VERSION.tar.gz"
RUN apk add --no-cache --upgrade \
curl \
tar
RUN mkdir /web-vault
WORKDIR /web-vault
SHELL ["/bin/ash", "-eo", "pipefail", "-c"]
RUN curl -L $URL | tar xz
RUN ls
# This hash is extracted from the docker web-vault builds and it's prefered over a simple tag because it's immutable.
# It can be viewed in multiple ways:
# - From the https://hub.docker.com/repository/docker/bitwardenrs/web-vault/tags page, click the tag name and the digest should be there.
# - From the console, with the following commands:
# docker pull bitwardenrs/web-vault:v2.13.2b
# docker image inspect --format "{{.RepoDigests}}" bitwardenrs/web-vault:v2.13.2b
#
# - To do the opposite, and get the tag from the hash, you can do:
# docker image inspect --format "{{.RepoTags}}" bitwardenrs/web-vault@sha256:f32c555a2bc3ee6bc0718319b1e8057c10ef889cf7231f0ff217af98486da554
FROM bitwardenrs/web-vault@sha256:f32c555a2bc3ee6bc0718319b1e8057c10ef889cf7231f0ff217af98486da554 as vault
########################## BUILD IMAGE ##########################
# We need to use the Rust build image, because
# we need the Rust compiler and Cargo tooling
FROM rust:1.40 as build
# set sqlite as default for DB ARG for backward comaptibility
# set sqlite as default for DB ARG for backward compatibility
ARG DB=sqlite
# Build time options to avoid dpkg warnings and help with reproducible builds.
ENV DEBIAN_FRONTEND=noninteractive LANG=C.UTF-8 TZ=UTC TERM=xterm-256color
# Don't download rust docs
RUN rustup set profile minimal
# Install required build libs for armhf architecture.
RUN sed 's/^deb/deb-src/' /etc/apt/sources.list > \
/etc/apt/sources.list.d/deb-src.list \
&& dpkg --add-architecture armhf \
&& apt-get update \
&& apt-get install -y \
--no-install-recommends \
libssl-dev:armhf \
libc6-dev:armhf
RUN apt-get update \
&& apt-get install -y \
--no-install-recommends \
@@ -42,29 +52,35 @@ RUN apt-get update \
ENV CARGO_HOME "/root/.cargo"
ENV USER "root"
# Creates a dummy project used to grab dependencies
RUN USER=root cargo new --bin /app
WORKDIR /app
# Prepare openssl armhf libs
RUN sed 's/^deb/deb-src/' /etc/apt/sources.list > \
/etc/apt/sources.list.d/deb-src.list \
&& dpkg --add-architecture armhf \
&& apt-get update \
&& apt-get install -y \
--no-install-recommends \
libssl-dev:armhf \
libc6-dev:armhf
# Copies over *only* your manifests and build files
COPY ./Cargo.* ./
COPY ./rust-toolchain ./rust-toolchain
COPY ./build.rs ./build.rs
ENV CC_armv7_unknown_linux_gnueabihf="/usr/bin/arm-linux-gnueabihf-gcc"
ENV CROSS_COMPILE="1"
ENV OPENSSL_INCLUDE_DIR="/usr/include/arm-linux-gnueabihf"
ENV OPENSSL_LIB_DIR="/usr/lib/arm-linux-gnueabihf"
RUN rustup target add armv7-unknown-linux-gnueabihf
# Builds your dependencies and removes the
# dummy project, except the target folder
# This folder contains the compiled dependencies
RUN cargo build --features ${DB} --release
RUN find . -not -path "./target*" -delete
# Copies the complete project
# To avoid copying unneeded files, use .dockerignore
COPY . .
# Build
RUN rustup target add armv7-unknown-linux-gnueabihf
# Make sure that we actually build the project
RUN touch src/main.rs
# Builds again, this time it'll just be
# your actual source files being built
RUN cargo build --features ${DB} --release --target=armv7-unknown-linux-gnueabihf
######################## RUNTIME IMAGE ########################
@@ -93,6 +109,7 @@ RUN [ "cross-build-end" ]
VOLUME /data
EXPOSE 80
EXPOSE 3012
# Copies the files from the context (Rocket.toml file and web-vault)
# and the binary from the "build" stage to the current stage
@@ -100,9 +117,9 @@ COPY Rocket.toml .
COPY --from=vault /web-vault ./web-vault
COPY --from=build /app/target/armv7-unknown-linux-gnueabihf/release/bitwarden_rs .
COPY docker/healthcheck.sh ./healthcheck.sh
COPY docker/healthcheck.sh /healthcheck.sh
HEALTHCHECK --interval=30s --timeout=3s CMD sh healthcheck.sh || exit 1
HEALTHCHECK --interval=60s --timeout=10s CMD ["/healthcheck.sh"]
# Configures the startup!
WORKDIR /

57
docker/healthcheck.sh Normal file → Executable file
View File

@@ -1,8 +1,53 @@
#!/usr/bin/env sh
#!/bin/sh
if [ -z "$ROCKET_TLS"]
then
curl --fail http://localhost:${ROCKET_PORT:-"80"}/alive || exit 1
else
curl --insecure --fail https://localhost:${ROCKET_PORT:-"80"}/alive || exit 1
# Use the value of the corresponding env var (if present),
# or a default value otherwise.
: ${DATA_FOLDER:="data"}
: ${ROCKET_PORT:="80"}
CONFIG_FILE="${DATA_FOLDER}"/config.json
# Given a config key, return the corresponding config value from the
# config file. If the key doesn't exist, return an empty string.
get_config_val() {
local key="$1"
# Extract a line of the form:
# "domain": "https://bw.example.com/path",
grep "\"${key}\":" "${CONFIG_FILE}" |
# To extract just the value (https://bw.example.com/path), delete:
# (1) everything up to and including the first ':',
# (2) whitespace and '"' from the front,
# (3) ',' and '"' from the back.
sed -e 's/[^:]\+://' -e 's/^[ "]\+//' -e 's/[,"]\+$//'
}
# Extract the base path from a domain URL. For example:
# - `` -> ``
# - `https://bw.example.com` -> ``
# - `https://bw.example.com/` -> ``
# - `https://bw.example.com/path` -> `/path`
# - `https://bw.example.com/multi/path` -> `/multi/path`
get_base_path() {
echo "$1" |
# Delete:
# (1) everything up to and including '://',
# (2) everything up to '/',
# (3) trailing '/' from the back.
sed -e 's|.*://||' -e 's|[^/]\+||' -e 's|/*$||'
}
# Read domain URL from config.json, if present.
if [ -r "${CONFIG_FILE}" ]; then
domain="$(get_config_val 'domain')"
if [ -n "${domain}" ]; then
# config.json 'domain' overrides the DOMAIN env var.
DOMAIN="${domain}"
fi
fi
base_path="$(get_base_path "${DOMAIN}")"
if [ -n "${ROCKET_TLS}" ]; then
s='s'
fi
curl --insecure --fail --silent --show-error \
"http${s}://localhost:${ROCKET_PORT}${base_path}/alive" || exit 1

17
docker/render_template Executable file
View File

@@ -0,0 +1,17 @@
#!/usr/bin/env python3
import os, argparse, json
import jinja2
args_parser = argparse.ArgumentParser()
args_parser.add_argument('template_file', help='Jinja2 template file to render.')
args_parser.add_argument('render_vars', help='JSON-encoded data to pass to the templating engine.')
cli_args = args_parser.parse_args()
render_vars = json.loads(cli_args.render_vars)
environment = jinja2.Environment(
loader=jinja2.FileSystemLoader(os.getcwd()),
trim_blocks=True,
)
print(environment.get_template(cli_args.template_file).render(render_vars))

View File

@@ -0,0 +1 @@
DROP TABLE org_policies;

View File

@@ -0,0 +1,9 @@
CREATE TABLE org_policies (
uuid CHAR(36) NOT NULL PRIMARY KEY,
org_uuid CHAR(36) NOT NULL REFERENCES organizations (uuid),
atype INTEGER NOT NULL,
enabled BOOLEAN NOT NULL,
data TEXT NOT NULL,
UNIQUE (org_uuid, atype)
);

View File

@@ -0,0 +1 @@
DROP TABLE org_policies;

View File

@@ -0,0 +1,9 @@
CREATE TABLE org_policies (
uuid CHAR(36) NOT NULL PRIMARY KEY,
org_uuid CHAR(36) NOT NULL REFERENCES organizations (uuid),
atype INTEGER NOT NULL,
enabled BOOLEAN NOT NULL,
data TEXT NOT NULL,
UNIQUE (org_uuid, atype)
);

View File

@@ -0,0 +1 @@
DROP TABLE org_policies;

View File

@@ -0,0 +1,9 @@
CREATE TABLE org_policies (
uuid TEXT NOT NULL PRIMARY KEY,
org_uuid TEXT NOT NULL REFERENCES organizations (uuid),
atype INTEGER NOT NULL,
enabled BOOLEAN NOT NULL,
data TEXT NOT NULL,
UNIQUE (org_uuid, atype)
);

View File

@@ -1 +1 @@
nightly-2019-12-19
nightly-2020-04-08

View File

@@ -1,3 +1,4 @@
use once_cell::sync::Lazy;
use serde_json::Value;
use std::process::Command;
@@ -16,7 +17,7 @@ use crate::mail;
use crate::CONFIG;
pub fn routes() -> Vec<Route> {
if CONFIG.admin_token().is_none() && !CONFIG.disable_admin_token() {
if !CONFIG.disable_admin_token() && !CONFIG.is_admin_token_set() {
return routes![admin_disabled];
}
@@ -34,12 +35,12 @@ pub fn routes() -> Vec<Route> {
post_config,
delete_config,
backup_db,
test_smtp,
]
}
lazy_static! {
static ref CAN_BACKUP: bool = cfg!(feature = "sqlite") && Command::new("sqlite3").arg("-version").status().is_ok();
}
static CAN_BACKUP: Lazy<bool> =
Lazy::new(|| cfg!(feature = "sqlite") && Command::new("sqlite3").arg("-version").status().is_ok());
#[get("/")]
fn admin_disabled() -> &'static str {
@@ -50,13 +51,17 @@ const COOKIE_NAME: &str = "BWRS_ADMIN";
const ADMIN_PATH: &str = "/admin";
const BASE_TEMPLATE: &str = "admin/base";
const VERSION: Option<&str> = option_env!("GIT_VERSION");
const VERSION: Option<&str> = option_env!("BWRS_VERSION");
fn admin_path() -> String {
format!("{}{}", CONFIG.domain_path(), ADMIN_PATH)
}
#[get("/", rank = 2)]
fn admin_login(flash: Option<FlashMessage>) -> ApiResult<Html<String>> {
// If there is an error, show it
let msg = flash.map(|msg| format!("{}: {}", msg.name(), msg.msg()));
let json = json!({"page_content": "admin/login", "version": VERSION, "error": msg});
let json = json!({"page_content": "admin/login", "version": VERSION, "error": msg, "urlpath": CONFIG.domain_path()});
// Return the page
let text = CONFIG.render_template(BASE_TEMPLATE, &json)?;
@@ -76,7 +81,7 @@ fn post_admin_login(data: Form<LoginForm>, mut cookies: Cookies, ip: ClientIp) -
if !_validate_token(&data.token) {
error!("Invalid admin token. IP: {}", ip.ip);
Err(Flash::error(
Redirect::to(ADMIN_PATH),
Redirect::to(admin_path()),
"Invalid admin token, please try again.",
))
} else {
@@ -85,14 +90,14 @@ fn post_admin_login(data: Form<LoginForm>, mut cookies: Cookies, ip: ClientIp) -
let jwt = encode_jwt(&claims);
let cookie = Cookie::build(COOKIE_NAME, jwt)
.path(ADMIN_PATH)
.max_age(chrono::Duration::minutes(20))
.path(admin_path())
.max_age(time::Duration::minutes(20))
.same_site(SameSite::Strict)
.http_only(true)
.finish();
cookies.add(cookie);
Ok(Redirect::to(ADMIN_PATH))
Ok(Redirect::to(admin_path()))
}
}
@@ -111,6 +116,7 @@ struct AdminTemplateData {
config: Value,
can_backup: bool,
logged_in: bool,
urlpath: String,
}
impl AdminTemplateData {
@@ -122,6 +128,7 @@ impl AdminTemplateData {
config: CONFIG.prepare_json(),
can_backup: *CAN_BACKUP,
logged_in: true,
urlpath: CONFIG.domain_path(),
}
}
@@ -153,26 +160,33 @@ fn invite_user(data: Json<InviteData>, _token: AdminToken, conn: DbConn) -> Empt
err!("User already exists")
}
if !CONFIG.invitations_allowed() {
err!("Invitations are not allowed")
}
let mut user = User::new(email);
user.save(&conn)?;
if CONFIG.mail_enabled() {
let org_name = "bitwarden_rs";
mail::send_invite(&user.email, &user.uuid, None, None, &org_name, None)
mail::send_invite(&user.email, &user.uuid, None, None, &CONFIG.invitation_org_name(), None)
} else {
let invitation = Invitation::new(data.email);
invitation.save(&conn)
}
}
#[post("/test/smtp", data = "<data>")]
fn test_smtp(data: Json<InviteData>, _token: AdminToken) -> EmptyResult {
let data: InviteData = data.into_inner();
let email = data.email.clone();
if CONFIG.mail_enabled() {
mail::send_test(&email)
} else {
err!("Mail is not enabled")
}
}
#[get("/logout")]
fn logout(mut cookies: Cookies) -> Result<Redirect, ()> {
cookies.remove(Cookie::named(COOKIE_NAME));
Ok(Redirect::to(ADMIN_PATH))
Ok(Redirect::to(admin_path()))
}
#[get("/users")]

View File

@@ -79,6 +79,9 @@ fn sync(data: Form<SyncData>, headers: Headers, conn: DbConn) -> JsonResult {
let collections = Collection::find_by_user_uuid(&headers.user.uuid, &conn);
let collections_json: Vec<Value> = collections.iter().map(Collection::to_json).collect();
let policies = OrgPolicy::find_by_user(&headers.user.uuid, &conn);
let policies_json: Vec<Value> = policies.iter().map(OrgPolicy::to_json).collect();
let ciphers = Cipher::find_by_user(&headers.user.uuid, &conn);
let ciphers_json: Vec<Value> = ciphers
.iter()
@@ -95,6 +98,7 @@ fn sync(data: Form<SyncData>, headers: Headers, conn: DbConn) -> JsonResult {
"Profile": user_json,
"Folders": folders_json,
"Collections": collections_json,
"Policies": policies_json,
"Ciphers": ciphers_json,
"Domains": domains_json,
"Object": "sync"
@@ -599,10 +603,14 @@ fn share_cipher_by_uuid(
None => err!("Cipher doesn't exist"),
};
match data.Cipher.OrganizationId.clone() {
None => err!("Organization id not provided"),
Some(organization_uuid) => {
let mut shared_to_collection = false;
match data.Cipher.OrganizationId.clone() {
// If we don't get an organization ID, we don't do anything
// No error because this is used when using the Clone functionality
None => {},
Some(organization_uuid) => {
for uuid in &data.CollectionIds {
match Collection::find_by_uuid_and_org(uuid, &organization_uuid, &conn) {
None => err!("Invalid collection ID provided"),
@@ -616,6 +624,9 @@ fn share_cipher_by_uuid(
}
}
}
}
};
update_cipher_from_data(
&mut cipher,
data.Cipher,
@@ -628,8 +639,6 @@ fn share_cipher_by_uuid(
Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, &conn)))
}
}
}
#[post("/ciphers/<uuid>/attachment", format = "multipart/form-data", data = "<data>")]
fn post_attachment(
@@ -642,20 +651,49 @@ fn post_attachment(
) -> JsonResult {
let cipher = match Cipher::find_by_uuid(&uuid, &conn) {
Some(cipher) => cipher,
None => err!("Cipher doesn't exist"),
None => err_discard!("Cipher doesn't exist", data),
};
if !cipher.is_write_accessible_to_user(&headers.user.uuid, &conn) {
err!("Cipher is not write accessible")
err_discard!("Cipher is not write accessible", data)
}
let mut params = content_type.params();
let boundary_pair = params.next().expect("No boundary provided");
let boundary = boundary_pair.1;
let size_limit = if let Some(ref user_uuid) = cipher.user_uuid {
match CONFIG.user_attachment_limit() {
Some(0) => err_discard!("Attachments are disabled", data),
Some(limit_kb) => {
let left = (limit_kb * 1024) - Attachment::size_by_user(user_uuid, &conn);
if left <= 0 {
err_discard!("Attachment size limit reached! Delete some files to open space", data)
}
Some(left as u64)
}
None => None,
}
} else if let Some(ref org_uuid) = cipher.organization_uuid {
match CONFIG.org_attachment_limit() {
Some(0) => err_discard!("Attachments are disabled", data),
Some(limit_kb) => {
let left = (limit_kb * 1024) - Attachment::size_by_org(org_uuid, &conn);
if left <= 0 {
err_discard!("Attachment size limit reached! Delete some files to open space", data)
}
Some(left as u64)
}
None => None,
}
} else {
err_discard!("Cipher is neither owned by a user nor an organization", data);
};
let base_path = Path::new(&CONFIG.attachments_folder()).join(&cipher.uuid);
let mut attachment_key = None;
let mut error = None;
Multipart::with_body(data.open(), boundary)
.foreach_entry(|mut field| {
@@ -674,18 +712,21 @@ fn post_attachment(
let file_name = HEXLOWER.encode(&crypto::get_random(vec![0; 10]));
let path = base_path.join(&file_name);
let size = match field.data.save().memory_threshold(0).size_limit(None).with_path(path) {
let size = match field.data.save().memory_threshold(0).size_limit(size_limit).with_path(path.clone()) {
SaveResult::Full(SavedData::File(_, size)) => size as i32,
SaveResult::Full(other) => {
error!("Attachment is not a file: {:?}", other);
std::fs::remove_file(path).ok();
error = Some(format!("Attachment is not a file: {:?}", other));
return;
}
SaveResult::Partial(_, reason) => {
error!("Partial result: {:?}", reason);
std::fs::remove_file(path).ok();
error = Some(format!("Attachment size limit exceeded with this file: {:?}", reason));
return;
}
SaveResult::Error(e) => {
error!("Error: {:?}", e);
std::fs::remove_file(path).ok();
error = Some(format!("Error: {:?}", e));
return;
}
};
@@ -699,6 +740,10 @@ fn post_attachment(
})
.expect("Error processing multipart data");
if let Some(ref e) = error {
err!(e);
}
nt.send_cipher_update(UpdateType::CipherUpdate, &cipher, &cipher.update_users_revision(&conn));
Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, &conn)))

View File

@@ -146,10 +146,10 @@ fn hibp_breach(username: String) -> JsonResult {
username
);
use reqwest::{header::USER_AGENT, Client};
use reqwest::{header::USER_AGENT, blocking::Client};
if let Some(api_key) = crate::CONFIG.hibp_api_key() {
let hibp_client = Client::builder().use_sys_proxy().build()?;
let hibp_client = Client::builder().build()?;
let res = hibp_client
.get(&url)
@@ -172,7 +172,7 @@ fn hibp_breach(username: String) -> JsonResult {
"BreachDate": "2019-08-18T00:00:00Z",
"AddedDate": "2019-08-18T00:00:00Z",
"Description": format!("Go to: <a href=\"https://haveibeenpwned.com/account/{account}\" target=\"_blank\" rel=\"noopener\">https://haveibeenpwned.com/account/{account}</a> for a manual check.<br/><br/>HaveIBeenPwned API key not set!<br/>Go to <a href=\"https://haveibeenpwned.com/API/Key\" target=\"_blank\" rel=\"noopener\">https://haveibeenpwned.com/API/Key</a> to purchase an API key from HaveIBeenPwned.<br/><br/>", account=username),
"LogoPath": "/bwrs_static/hibp.png",
"LogoPath": "bwrs_static/hibp.png",
"PwnCount": 0,
"DataClasses": [
"Error - No API key set!"

View File

@@ -2,6 +2,7 @@ use rocket::request::Form;
use rocket::Route;
use rocket_contrib::json::Json;
use serde_json::Value;
use num_traits::FromPrimitive;
use crate::api::{
EmptyResult, JsonResult, JsonUpcase, JsonUpcaseVec, Notify, NumberOrString, PasswordData, UpdateType,
@@ -45,6 +46,10 @@ pub fn routes() -> Vec<Route> {
delete_user,
post_delete_user,
post_org_import,
list_policies,
list_policies_token,
get_policy,
put_policy,
]
}
@@ -830,22 +835,13 @@ struct RelationsData {
fn post_org_import(
query: Form<OrgIdData>,
data: JsonUpcase<ImportData>,
headers: Headers,
headers: AdminHeaders,
conn: DbConn,
nt: Notify,
) -> EmptyResult {
let data: ImportData = data.into_inner().data;
let org_id = query.into_inner().organization_id;
let org_user = match UserOrganization::find_by_user_and_org(&headers.user.uuid, &org_id, &conn) {
Some(user) => user,
None => err!("User is not part of the organization"),
};
if org_user.atype < UserOrgType::Admin {
err!("Only admins or owners can import into an organization")
}
// Read and create the collections
let collections: Vec<_> = data
.Collections
@@ -866,6 +862,8 @@ fn post_org_import(
relations.push((relation.Key, relation.Value));
}
let headers: Headers = headers.into();
// Read and create the ciphers
let ciphers: Vec<_> = data
.Ciphers
@@ -901,3 +899,83 @@ fn post_org_import(
let mut user = headers.user;
user.update_revision(&conn)
}
#[get("/organizations/<org_id>/policies")]
fn list_policies(org_id: String, _headers: AdminHeaders, conn: DbConn) -> JsonResult {
let policies = OrgPolicy::find_by_org(&org_id, &conn);
let policies_json: Vec<Value> = policies.iter().map(OrgPolicy::to_json).collect();
Ok(Json(json!({
"Data": policies_json,
"Object": "list",
"ContinuationToken": null
})))
}
#[get("/organizations/<org_id>/policies/token?<token>")]
fn list_policies_token(org_id: String, token: String, conn: DbConn) -> JsonResult {
let invite = crate::auth::decode_invite(&token)?;
let invite_org_id = match invite.org_id {
Some(invite_org_id) => invite_org_id,
None => err!("Invalid token"),
};
if invite_org_id != org_id {
err!("Token doesn't match request organization");
}
// TODO: We receive the invite token as ?token=<>, validate it contains the org id
let policies = OrgPolicy::find_by_org(&org_id, &conn);
let policies_json: Vec<Value> = policies.iter().map(OrgPolicy::to_json).collect();
Ok(Json(json!({
"Data": policies_json,
"Object": "list",
"ContinuationToken": null
})))
}
#[get("/organizations/<org_id>/policies/<pol_type>")]
fn get_policy(org_id: String, pol_type: i32, _headers: AdminHeaders, conn: DbConn) -> JsonResult {
let pol_type_enum = match OrgPolicyType::from_i32(pol_type) {
Some(pt) => pt,
None => err!("Invalid policy type"),
};
let policy = match OrgPolicy::find_by_org_and_type(&org_id, pol_type, &conn) {
Some(p) => p,
None => OrgPolicy::new(org_id, pol_type_enum, "{}".to_string()),
};
Ok(Json(policy.to_json()))
}
#[derive(Deserialize)]
struct PolicyData {
enabled: bool,
#[serde(rename = "type")]
_type: i32,
data: Value,
}
#[put("/organizations/<org_id>/policies/<pol_type>", data = "<data>")]
fn put_policy(org_id: String, pol_type: i32, data: Json<PolicyData>, _headers: AdminHeaders, conn: DbConn) -> JsonResult {
let data: PolicyData = data.into_inner();
let pol_type_enum = match OrgPolicyType::from_i32(pol_type) {
Some(pt) => pt,
None => err!("Invalid policy type"),
};
let mut policy = match OrgPolicy::find_by_org_and_type(&org_id, pol_type, &conn) {
Some(p) => p,
None => OrgPolicy::new(org_id, pol_type_enum, "{}".to_string()),
};
policy.enabled = data.enabled;
policy.data = serde_json::to_string(&data.data)?;
policy.save(&conn)?;
Ok(Json(policy.to_json()))
}

View File

@@ -21,9 +21,9 @@ pub fn routes() -> Vec<Route> {
#[derive(Serialize, Deserialize)]
struct DuoData {
host: String,
ik: String,
sk: String,
host: String, // Duo API hostname
ik: String, // integration key
sk: String, // secret key
}
impl DuoData {
@@ -187,9 +187,10 @@ fn activate_duo_put(data: JsonUpcase<EnableDuoData>, headers: Headers, conn: DbC
fn duo_api_request(method: &str, path: &str, params: &str, data: &DuoData) -> EmptyResult {
const AGENT: &str = "bitwarden_rs:Duo/1.0 (Rust)";
use reqwest::{header::*, Client, Method};
use reqwest::{header::*, Method, blocking::Client};
use std::str::FromStr;
// https://duo.com/docs/authapi#api-details
let url = format!("https://{}{}", &data.host, path);
let date = Utc::now().to_rfc2822();
let username = &data.ik;
@@ -268,6 +269,10 @@ fn sign_duo_values(key: &str, email: &str, ikey: &str, prefix: &str, expire: i64
}
pub fn validate_duo_login(email: &str, response: &str, conn: &DbConn) -> EmptyResult {
// email is as entered by the user, so it needs to be normalized before
// comparison with auth_user below.
let email = &email.to_lowercase();
let split: Vec<&str> = response.split(':').collect();
if split.len() != 2 {
err!("Invalid response length");

View File

@@ -271,10 +271,10 @@ impl EmailTokenData {
/// Takes an email address and obscures it by replacing it with asterisks except two characters.
pub fn obscure_email(email: &str) -> String {
let split: Vec<&str> = email.split('@').collect();
let split: Vec<&str> = email.rsplitn(2, '@').collect();
let mut name = split[0].to_string();
let domain = &split[1];
let mut name = split[1].to_string();
let domain = &split[0];
let name_size = name.chars().count();

View File

@@ -1,3 +1,4 @@
use once_cell::sync::Lazy;
use rocket::Route;
use rocket_contrib::json::Json;
use serde_json;
@@ -18,10 +19,8 @@ use crate::CONFIG;
const U2F_VERSION: &str = "U2F_V2";
lazy_static! {
static ref APP_ID: String = format!("{}/app-id.json", &CONFIG.domain());
static ref U2F: U2f = U2f::new(APP_ID.clone());
}
static APP_ID: Lazy<String> = Lazy::new(|| format!("{}/app-id.json", &CONFIG.domain()));
static U2F: Lazy<U2f> = Lazy::new(|| U2f::new(APP_ID.clone()));
pub fn routes() -> Vec<Route> {
routes![
@@ -92,6 +91,7 @@ struct RegistrationDef {
key_handle: Vec<u8>,
pub_key: Vec<u8>,
attestation_cert: Option<Vec<u8>>,
device_name: Option<String>,
}
#[derive(Serialize, Deserialize)]

View File

@@ -1,3 +1,4 @@
use once_cell::sync::Lazy;
use std::fs::{create_dir_all, remove_file, symlink_metadata, File};
use std::io::prelude::*;
use std::net::ToSocketAddrs;
@@ -7,7 +8,7 @@ use rocket::http::ContentType;
use rocket::response::Content;
use rocket::Route;
use reqwest::{header::HeaderMap, Client, Response, Url};
use reqwest::{Url, header::HeaderMap, blocking::Client, blocking::Response};
use rocket::http::Cookie;
@@ -16,6 +17,7 @@ use soup::prelude::*;
use crate::error::Error;
use crate::CONFIG;
use crate::util::Cached;
pub fn routes() -> Vec<Route> {
routes![icon]
@@ -25,16 +27,14 @@ const FALLBACK_ICON: &[u8; 344] = include_bytes!("../static/fallback-icon.png");
const ALLOWED_CHARS: &str = "_-.";
lazy_static! {
static CLIENT: Lazy<Client> = Lazy::new(|| {
// Reuse the client between requests
static ref CLIENT: Client = Client::builder()
.use_sys_proxy()
.gzip(true)
Client::builder()
.timeout(Duration::from_secs(CONFIG.icon_download_timeout()))
.default_headers(_header_map())
.build()
.unwrap();
}
.unwrap()
});
fn is_valid_domain(domain: &str) -> bool {
// Don't allow empty or too big domains or path traversal
@@ -53,15 +53,15 @@ fn is_valid_domain(domain: &str) -> bool {
}
#[get("/<domain>/icon.png")]
fn icon(domain: String) -> Content<Vec<u8>> {
fn icon(domain: String) -> Cached<Content<Vec<u8>>> {
let icon_type = ContentType::new("image", "x-icon");
if !is_valid_domain(&domain) {
warn!("Invalid domain: {:#?}", domain);
return Content(icon_type, FALLBACK_ICON.to_vec());
return Cached::long(Content(icon_type, FALLBACK_ICON.to_vec()));
}
Content(icon_type, get_icon(&domain))
Cached::long(Content(icon_type, get_icon(&domain)))
}
fn check_icon_domain_is_blacklisted(domain: &str) -> bool {

View File

@@ -347,6 +347,7 @@ fn _json_err_twofactor(providers: &[i32], user_uuid: &str, conn: &DbConn) -> Api
Ok(result)
}
// https://github.com/bitwarden/mobile/blob/master/src/Core/Models/Request/TokenRequest.cs
#[derive(Debug, Clone, Default)]
#[allow(non_snake_case)]
struct ConnectData {
@@ -364,6 +365,7 @@ struct ConnectData {
device_identifier: Option<String>,
device_name: Option<String>,
device_type: Option<String>,
device_push_token: Option<String>, // Unused; mobile device push not yet supported.
// Needed for two-factor auth
two_factor_provider: Option<i32>,
@@ -391,6 +393,7 @@ impl<'f> FromForm<'f> for ConnectData {
"deviceidentifier" => form.device_identifier = Some(value),
"devicename" => form.device_name = Some(value),
"devicetype" => form.device_type = Some(value),
"devicepushtoken" => form.device_push_token = Some(value),
"twofactorprovider" => form.two_factor_provider = value.parse().ok(),
"twofactortoken" => form.two_factor_token = Some(value),
"twofactorremember" => form.two_factor_remember = value.parse().ok(),

View File

@@ -152,6 +152,9 @@ impl WSHandler {
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`).
let path = hs.request.resource();
let (_id, access_token) = match path.split('?').nth(1) {
@@ -170,10 +173,11 @@ impl Handler for WSHandler {
match (id, access_token) {
(Some(a), Some(b)) => (a, b),
_ => return self.err("Missing id or access token"),
(None, Some(b)) => ("", b), // Ignore missing `id`.
_ => return self.err("Missing access token"),
}
}
None => return self.err("Missing query path"),
None => return self.err("Missing query parameters"),
};
// Validate the user

View File

@@ -37,7 +37,17 @@ fn app_id() -> Cached<Content<Json<Value>>> {
{
"version": { "major": 1, "minor": 0 },
"ids": [
&CONFIG.domain(),
// Per <https://fidoalliance.org/specs/fido-v2.0-id-20180227/fido-appid-and-facets-v2.0-id-20180227.html#determining-the-facetid-of-a-calling-application>:
//
// "In the Web case, the FacetID MUST be the Web Origin [RFC6454]
// of the web page triggering the FIDO operation, written as
// a URI with an empty path. Default ports are omitted and any
// path component is ignored."
//
// This leaves it unclear as to whether the path must be empty,
// or whether it can be non-empty and will be ignored. To be on
// the safe side, use a proper web origin (with empty path).
&CONFIG.domain_origin(),
"ios:bundle-id:com.8bit.bitwarden",
"android:apk-key-hash:dUGFzUzf3lmHSLBDBIv+WaFyZMI" ]
}]
@@ -75,6 +85,6 @@ fn static_files(filename: String) -> Result<Content<&'static [u8]>, Error> {
"bootstrap-native-v4.js" => Ok(Content(ContentType::JavaScript, include_bytes!("../static/scripts/bootstrap-native-v4.js"))),
"md5.js" => Ok(Content(ContentType::JavaScript, include_bytes!("../static/scripts/md5.js"))),
"identicon.js" => Ok(Content(ContentType::JavaScript, include_bytes!("../static/scripts/identicon.js"))),
_ => err!("Image not found"),
_ => err!(format!("Static file not found: {}", filename)),
}
}

View File

@@ -3,8 +3,10 @@
//
use crate::util::read_file;
use chrono::{Duration, Utc};
use once_cell::sync::Lazy;
use num_traits::FromPrimitive;
use jsonwebtoken::{self, Algorithm, Header};
use jsonwebtoken::{self, Algorithm, Header, EncodingKey, DecodingKey};
use serde::de::DeserializeOwned;
use serde::ser::Serialize;
@@ -13,26 +15,24 @@ use crate::CONFIG;
const JWT_ALGORITHM: Algorithm = Algorithm::RS256;
lazy_static! {
pub static ref DEFAULT_VALIDITY: Duration = Duration::hours(2);
static ref JWT_HEADER: Header = Header::new(JWT_ALGORITHM);
pub static ref JWT_LOGIN_ISSUER: String = format!("{}|login", CONFIG.domain());
pub static ref JWT_INVITE_ISSUER: String = format!("{}|invite", CONFIG.domain());
pub static ref JWT_DELETE_ISSUER: String = format!("{}|delete", CONFIG.domain());
pub static ref JWT_VERIFYEMAIL_ISSUER: String = format!("{}|verifyemail", CONFIG.domain());
pub static ref JWT_ADMIN_ISSUER: String = format!("{}|admin", CONFIG.domain());
static ref PRIVATE_RSA_KEY: Vec<u8> = match read_file(&CONFIG.private_rsa_key()) {
pub static DEFAULT_VALIDITY: Lazy<Duration> = Lazy::new(|| Duration::hours(2));
static JWT_HEADER: Lazy<Header> = Lazy::new(|| Header::new(JWT_ALGORITHM));
pub static JWT_LOGIN_ISSUER: Lazy<String> = Lazy::new(|| format!("{}|login", CONFIG.domain_origin()));
static JWT_INVITE_ISSUER: Lazy<String> = Lazy::new(|| format!("{}|invite", CONFIG.domain_origin()));
static JWT_DELETE_ISSUER: Lazy<String> = Lazy::new(|| format!("{}|delete", CONFIG.domain_origin()));
static JWT_VERIFYEMAIL_ISSUER: Lazy<String> = Lazy::new(|| format!("{}|verifyemail", CONFIG.domain_origin()));
static JWT_ADMIN_ISSUER: Lazy<String> = Lazy::new(|| format!("{}|admin", CONFIG.domain_origin()));
static PRIVATE_RSA_KEY: Lazy<Vec<u8>> = Lazy::new(|| match read_file(&CONFIG.private_rsa_key()) {
Ok(key) => key,
Err(e) => panic!("Error loading private RSA Key.\n Error: {}", e),
};
static ref PUBLIC_RSA_KEY: Vec<u8> = match read_file(&CONFIG.public_rsa_key()) {
});
static PUBLIC_RSA_KEY: Lazy<Vec<u8>> = Lazy::new(|| match read_file(&CONFIG.public_rsa_key()) {
Ok(key) => key,
Err(e) => panic!("Error loading public RSA Key.\n Error: {}", e),
};
}
});
pub fn encode_jwt<T: Serialize>(claims: &T) -> String {
match jsonwebtoken::encode(&JWT_HEADER, claims, &PRIVATE_RSA_KEY) {
match jsonwebtoken::encode(&JWT_HEADER, claims, &EncodingKey::from_rsa_der(&PRIVATE_RSA_KEY)) {
Ok(token) => token,
Err(e) => panic!("Error encoding jwt {}", e),
}
@@ -51,7 +51,7 @@ fn decode_jwt<T: DeserializeOwned>(token: &str, issuer: String) -> Result<T, Err
let token = token.replace(char::is_whitespace, "");
jsonwebtoken::decode(&token, &PUBLIC_RSA_KEY, &validation)
jsonwebtoken::decode(&token, &DecodingKey::from_rsa_der(&PUBLIC_RSA_KEY), &validation)
.map(|d| d.claims)
.map_res("Error decoding JWT")
}
@@ -307,6 +307,25 @@ pub struct OrgHeaders {
pub org_user_type: UserOrgType,
}
// org_id is usually the second param ("/organizations/<org_id>")
// But there are cases where it is located in a query value.
// First check the param, if this is not a valid uuid, we will try the query value.
fn get_org_id(request: &Request) -> Option<String> {
if let Some(Ok(org_id)) = request.get_param::<String>(1) {
if uuid::Uuid::parse_str(&org_id).is_ok() {
return Some(org_id);
}
}
if let Some(Ok(org_id)) = request.get_query_value::<String>("organizationId") {
if uuid::Uuid::parse_str(&org_id).is_ok() {
return Some(org_id);
}
}
None
}
impl<'a, 'r> FromRequest<'a, 'r> for OrgHeaders {
type Error = &'static str;
@@ -315,9 +334,8 @@ impl<'a, 'r> FromRequest<'a, 'r> for OrgHeaders {
Outcome::Forward(_) => Outcome::Forward(()),
Outcome::Failure(f) => Outcome::Failure(f),
Outcome::Success(headers) => {
// org_id is expected to be the second param ("/organizations/<org_id>")
match request.get_param::<String>(1) {
Some(Ok(org_id)) => {
match get_org_id(request) {
Some(org_id) => {
let conn = match request.guard::<DbConn>() {
Outcome::Success(conn) => conn,
_ => err_handler!("Error getting DB"),
@@ -348,7 +366,7 @@ impl<'a, 'r> FromRequest<'a, 'r> for OrgHeaders {
}
},
})
}
},
_ => err_handler!("Error getting the organization id"),
}
}
@@ -386,6 +404,16 @@ impl<'a, 'r> FromRequest<'a, 'r> for AdminHeaders {
}
}
impl Into<Headers> for AdminHeaders {
fn into(self) -> Headers {
Headers {
host: self.host,
device: self.device,
user: self.user
}
}
}
pub struct OwnerHeaders {
pub host: String,
pub device: Device,

View File

@@ -1,19 +1,23 @@
use once_cell::sync::Lazy;
use std::process::exit;
use std::sync::RwLock;
use crate::error::Error;
use crate::util::get_env;
use reqwest::Url;
lazy_static! {
pub static ref CONFIG: Config = Config::load().unwrap_or_else(|e| {
println!("Error loading config:\n\t{:?}\n", e);
exit(12)
});
pub static ref CONFIG_FILE: String = {
use crate::error::Error;
use crate::util::{get_env, get_env_bool};
static CONFIG_FILE: Lazy<String> = Lazy::new(|| {
let data_folder = get_env("DATA_FOLDER").unwrap_or_else(|| String::from("data"));
get_env("CONFIG_FILE").unwrap_or_else(|| format!("{}/config.json", data_folder))
};
}
});
pub static CONFIG: Lazy<Config> = Lazy::new(|| {
Config::load().unwrap_or_else(|e| {
println!("Error loading config:\n\t{:?}\n", e);
exit(12)
})
});
pub type Pass = String;
@@ -23,13 +27,13 @@ macro_rules! make_config {
$group:ident $(: $group_enabled:ident)? {
$(
$(#[doc = $doc:literal])+
$name:ident : $ty:ty, $editable:literal, $none_action:ident $(, $default:expr)?;
$name:ident : $ty:ident, $editable:literal, $none_action:ident $(, $default:expr)?;
)+},
)+) => {
pub struct Config { inner: RwLock<Inner> }
struct Inner {
templates: Handlebars,
templates: Handlebars<'static>,
config: ConfigItems,
_env: ConfigBuilder,
@@ -50,7 +54,7 @@ macro_rules! make_config {
let mut builder = ConfigBuilder::default();
$($(
builder.$name = get_env(&stringify!($name).to_uppercase());
builder.$name = make_config! { @getenv &stringify!($name).to_uppercase(), $ty };
)+)+
builder
@@ -189,6 +193,10 @@ macro_rules! make_config {
let f: &dyn Fn(&ConfigItems) -> _ = &$default_fn;
f($config)
}};
( @getenv $name:expr, bool ) => { get_env_bool($name) };
( @getenv $name:expr, $ty:ident ) => { get_env($name) };
}
//STRUCTURE:
@@ -236,12 +244,21 @@ make_config! {
domain: String, true, def, "http://localhost".to_string();
/// Domain Set |> Indicates if the domain is set by the admin. Otherwise the default will be used.
domain_set: bool, false, def, false;
/// Domain origin |> Domain URL origin (in https://example.com:8443/path, https://example.com:8443 is the origin)
domain_origin: String, false, auto, |c| extract_url_origin(&c.domain);
/// Domain path |> Domain URL path (in https://example.com:8443/path, /path is the path)
domain_path: String, false, auto, |c| extract_url_path(&c.domain);
/// Enable web vault
web_vault_enabled: bool, false, def, true;
/// HIBP Api Key |> HaveIBeenPwned API Key, request it here: https://haveibeenpwned.com/API/Key
hibp_api_key: Pass, true, option;
/// Per-user attachment limit (KB) |> Limit in kilobytes for a users attachments, once the limit is exceeded it won't be possible to upload more
user_attachment_limit: i64, true, option;
/// Per-organization attachment limit (KB) |> Limit in kilobytes for an organization attachments, once the limit is exceeded it won't be possible to upload more
org_attachment_limit: i64, true, option;
/// Disable icon downloads |> Set to true to disable icon downloading, this would still serve icons from
/// $ICON_CACHE_FOLDER, but it won't produce any external network request. Needs to set $ICON_CACHE_TTL to 0,
/// otherwise it will delete them and they won't be downloaded again.
@@ -267,6 +284,9 @@ make_config! {
/// Admin page token |> The token used to authenticate in this very same page. Changing it here won't deauthorize the current session
admin_token: Pass, true, option;
/// Invitation organization name |> Name shown in the invitation emails that don't come from a specific organization
invitation_org_name: String, true, def, "Bitwarden_RS".to_string();
},
/// Advanced settings
@@ -319,6 +339,9 @@ make_config! {
/// Bypass admin page security (Know the risks!) |> Disables the Admin Token for the admin page so you may use your own auth in-front
disable_admin_token: bool, true, def, false;
/// Allowed iframe ancestors (Know the risks!) |> Allows other domains to embed the web vault into an iframe, useful for embedding into secure intranets
allowed_iframe_ancestors: String, true, def, String::new();
},
/// Yubikey settings
@@ -400,9 +423,15 @@ fn validate_config(cfg: &ConfigItems) -> Result<(), Error> {
err!("`DATABASE_URL` should start with postgresql: when using the PostgreSQL server")
}
let dom = cfg.domain.to_lowercase();
if !dom.starts_with("http://") && !dom.starts_with("https://") {
err!("DOMAIN variable needs to contain the protocol (http, https). Use 'http[s]://bw.example.com' instead of 'bw.example.com'");
}
if let Some(ref token) = cfg.admin_token {
if token.trim().is_empty() {
err!("`ADMIN_TOKEN` is enabled but has an empty value. To enable the admin page without token, use `DISABLE_ADMIN_TOKEN`")
if token.trim().is_empty() && !cfg.disable_admin_token {
println!("[WARNING] `ADMIN_TOKEN` is enabled but has an empty value, so the admin page will be disabled.");
println!("[WARNING] To enable the admin page without a token, use `DISABLE_ADMIN_TOKEN`.");
}
}
@@ -442,6 +471,29 @@ fn validate_config(cfg: &ConfigItems) -> Result<(), Error> {
Ok(())
}
/// Extracts an RFC 6454 web origin from a URL.
fn extract_url_origin(url: &str) -> String {
match Url::parse(url) {
Ok(u) => u.origin().ascii_serialization(),
Err(e) => {
println!("Error validating domain: {}", e);
String::new()
}
}
}
/// Extracts the path from a URL.
/// All trailing '/' chars are trimmed, even if the path is a lone '/'.
fn extract_url_path(url: &str) -> String {
match Url::parse(url) {
Ok(u) => u.path().trim_end_matches('/').to_string(),
Err(_) => {
// We already print it in the method above, no need to do it again
String::new()
}
}
}
impl Config {
pub fn load() -> Result<Self, Error> {
// Loading from env and file
@@ -506,7 +558,12 @@ impl Config {
warn!("Failed to parse email address '{}'", email);
return false;
}
self.signups_domains_whitelist().split(',').any(|d| d == e[0])
// Allow signups if the whitelist is empty/not configured
// (it doesn't contain any domains), or if it matches at least
// one domain.
let whitelist_str = self.signups_domains_whitelist();
( whitelist_str.is_empty() && CONFIG.signups_allowed() )|| whitelist_str.split(',').filter(|s| !s.is_empty()).any(|d| d == e[0])
}
pub fn delete_user_config(&self) -> Result<(), Error> {
@@ -561,6 +618,13 @@ impl Config {
}
}
/// Tests whether the admin token is set to a non-empty value.
pub fn is_admin_token_set(&self) -> bool {
let token = self.admin_token();
!token.is_none() && !token.unwrap().trim().is_empty()
}
pub fn render_template<T: serde::ser::Serialize>(
&self,
name: &str,
@@ -568,7 +632,7 @@ impl Config {
) -> Result<String, crate::error::Error> {
if CONFIG.reload_templates() {
warn!("RELOADING TEMPLATES");
let hb = load_templates(CONFIG.templates_folder().as_ref());
let hb = load_templates(CONFIG.templates_folder());
hb.render(name, data).map_err(Into::into)
} else {
let hb = &CONFIG.inner.read().unwrap().templates;
@@ -577,17 +641,18 @@ impl Config {
}
}
use handlebars::{
Context, Handlebars, Helper, HelperDef, HelperResult, Output, RenderContext, RenderError, Renderable,
};
use handlebars::{Context, Handlebars, Helper, HelperResult, Output, RenderContext, RenderError, Renderable};
fn load_templates(path: &str) -> Handlebars {
fn load_templates<P>(path: P) -> Handlebars<'static>
where
P: AsRef<std::path::Path>,
{
let mut hb = Handlebars::new();
// Error on missing params
hb.set_strict_mode(true);
// Register helpers
hb.register_helper("case", Box::new(CaseHelper));
hb.register_helper("jsesc", Box::new(JsEscapeHelper));
hb.register_helper("case", Box::new(case_helper));
hb.register_helper("jsesc", Box::new(js_escape_helper));
macro_rules! reg {
($name:expr) => {{
@@ -613,6 +678,7 @@ fn load_templates(path: &str) -> Handlebars {
reg!("email/verify_email", ".html");
reg!("email/welcome", ".html");
reg!("email/welcome_must_verify", ".html");
reg!("email/smtp_test", ".html");
reg!("admin/base");
reg!("admin/login");
@@ -626,18 +692,16 @@ fn load_templates(path: &str) -> Handlebars {
hb
}
pub struct CaseHelper;
impl HelperDef for CaseHelper {
fn call<'reg: 'rc, 'rc>(
&self,
fn case_helper<'reg, 'rc>(
h: &Helper<'reg, 'rc>,
r: &'reg Handlebars,
ctx: &Context,
rc: &mut RenderContext<'reg>,
ctx: &'rc Context,
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 \"case\""))?;
let param = h
.param(0)
.ok_or_else(|| RenderError::new("Param not found for helper \"case\""))?;
let value = param.value().clone();
if h.params().iter().skip(1).any(|x| x.value() == &value) {
@@ -646,23 +710,22 @@ impl HelperDef for CaseHelper {
Ok(())
}
}
}
pub struct JsEscapeHelper;
impl HelperDef for JsEscapeHelper {
fn call<'reg: 'rc, 'rc>(
&self,
fn js_escape_helper<'reg, 'rc>(
h: &Helper<'reg, 'rc>,
_: &'reg Handlebars,
_: &Context,
_: &mut RenderContext<'reg>,
_r: &'reg Handlebars,
_ctx: &'rc Context,
_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 \"js_escape\""))?;
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 \"js_escape\" is not a String"))?;
let escaped_value = value.replace('\\', "").replace('\'', "\\x22").replace('\"', "\\x27");
let quoted_value = format!("&quot;{}&quot;", escaped_value);
@@ -670,4 +733,3 @@ impl HelperDef for JsEscapeHelper {
out.write(&quoted_value)?;
Ok(())
}
}

View File

@@ -6,7 +6,7 @@ use crate::error::Error;
use ring::{digest, hmac, pbkdf2};
use std::num::NonZeroU32;
static DIGEST_ALG: &digest::Algorithm = &digest::SHA256;
static DIGEST_ALG: pbkdf2::Algorithm = pbkdf2::PBKDF2_HMAC_SHA256;
const OUTPUT_LEN: usize = digest::SHA256_OUTPUT_LEN;
pub fn hash_password(secret: &[u8], salt: &[u8], iterations: u32) -> Vec<u8> {
@@ -29,7 +29,7 @@ pub fn verify_password_hash(secret: &[u8], salt: &[u8], previous: &[u8], iterati
pub fn hmac_sign(key: &str, data: &str) -> String {
use data_encoding::HEXLOWER;
let key = hmac::SigningKey::new(&digest::SHA1, key.as_bytes());
let key = hmac::Key::new(hmac::HMAC_SHA1_FOR_LEGACY_USE_ONLY, key.as_bytes());
let signature = hmac::sign(&key, data.as_bytes());
HEXLOWER.encode(signature.as_ref())

View File

@@ -76,7 +76,8 @@ impl<'a, 'r> FromRequest<'a, 'r> for DbConn {
type Error = ();
fn from_request(request: &'a Request<'r>) -> request::Outcome<DbConn, ()> {
let pool = request.guard::<State<Pool>>()?;
// https://github.com/SergioBenitez/Rocket/commit/e3c1a4ad3ab9b840482ec6de4200d30df43e357c
let pool = try_outcome!(request.guard::<State<Pool>>());
match pool.get() {
Ok(conn) => Outcome::Success(DbConn(conn)),
Err(_) => Outcome::Failure((Status::ServiceUnavailable, ())),

View File

@@ -49,7 +49,7 @@ impl Attachment {
}
}
use crate::db::schema::attachments;
use crate::db::schema::{attachments, ciphers};
use crate::db::DbConn;
use diesel;
use diesel::prelude::*;
@@ -118,4 +118,26 @@ impl Attachment {
.load::<Self>(&**conn)
.expect("Error loading attachments")
}
pub fn size_by_user(user_uuid: &str, conn: &DbConn) -> i64 {
let result: Option<i64> = attachments::table
.left_join(ciphers::table.on(ciphers::uuid.eq(attachments::cipher_uuid)))
.filter(ciphers::user_uuid.eq(user_uuid))
.select(diesel::dsl::sum(attachments::file_size))
.first(&**conn)
.expect("Error loading user attachment total size");
result.unwrap_or(0)
}
pub fn size_by_org(org_uuid: &str, conn: &DbConn) -> i64 {
let result: Option<i64> = attachments::table
.left_join(ciphers::table.on(ciphers::uuid.eq(attachments::cipher_uuid)))
.filter(ciphers::organization_uuid.eq(org_uuid))
.select(diesel::dsl::sum(attachments::file_size))
.first(&**conn)
.expect("Error loading user attachment total size");
result.unwrap_or(0)
}
}

View File

@@ -80,7 +80,19 @@ impl Cipher {
let fields_json = self.fields.as_ref().and_then(|s| serde_json::from_str(s).ok()).unwrap_or(Value::Null);
let password_history_json = self.password_history.as_ref().and_then(|s| serde_json::from_str(s).ok()).unwrap_or(Value::Null);
let mut data_json: Value = serde_json::from_str(&self.data).unwrap_or(Value::Null);
// Get the data or a default empty value to avoid issues with the mobile apps
let mut data_json: Value = serde_json::from_str(&self.data).unwrap_or_else(|_| json!({
"Fields":null,
"Name": self.name,
"Notes":null,
"Password":null,
"PasswordHistory":null,
"PasswordRevisionDate":null,
"Response":null,
"Totp":null,
"Uris":null,
"Username":null
}));
// TODO: ******* Backwards compat start **********
// To remove backwards compatibility, just remove this entire section

View File

@@ -7,6 +7,7 @@ mod user;
mod collection;
mod organization;
mod two_factor;
mod org_policy;
pub use self::attachment::Attachment;
pub use self::cipher::Cipher;
@@ -17,3 +18,4 @@ pub use self::organization::Organization;
pub use self::organization::{UserOrgStatus, UserOrgType, UserOrganization};
pub use self::two_factor::{TwoFactor, TwoFactorType};
pub use self::user::{Invitation, User};
pub use self::org_policy::{OrgPolicy, OrgPolicyType};

142
src/db/models/org_policy.rs Normal file
View File

@@ -0,0 +1,142 @@
use diesel;
use diesel::prelude::*;
use serde_json::Value;
use crate::api::EmptyResult;
use crate::db::schema::org_policies;
use crate::db::DbConn;
use crate::error::MapResult;
use super::Organization;
#[derive(Debug, Identifiable, Queryable, Insertable, Associations, AsChangeset)]
#[table_name = "org_policies"]
#[belongs_to(Organization, foreign_key = "org_uuid")]
#[primary_key(uuid)]
pub struct OrgPolicy {
pub uuid: String,
pub org_uuid: String,
pub atype: i32,
pub enabled: bool,
pub data: String,
}
#[allow(dead_code)]
#[derive(FromPrimitive)]
pub enum OrgPolicyType {
TwoFactorAuthentication = 0,
MasterPassword = 1,
PasswordGenerator = 2,
}
/// Local methods
impl OrgPolicy {
pub fn new(org_uuid: String, atype: OrgPolicyType, data: String) -> Self {
Self {
uuid: crate::util::get_uuid(),
org_uuid,
atype: atype as i32,
enabled: false,
data,
}
}
pub fn to_json(&self) -> Value {
let data_json: Value = serde_json::from_str(&self.data).unwrap_or(Value::Null);
json!({
"Id": self.uuid,
"OrganizationId": self.org_uuid,
"Type": self.atype,
"Data": data_json,
"Enabled": self.enabled,
"Object": "policy",
})
}
}
/// Database methods
impl OrgPolicy {
#[cfg(feature = "postgresql")]
pub fn save(&self, conn: &DbConn) -> EmptyResult {
// We need to make sure we're not going to violate the unique constraint on org_uuid and atype.
// This happens automatically on other DBMS backends due to replace_into(). PostgreSQL does
// not support multiple constraints on ON CONFLICT clauses.
diesel::delete(
org_policies::table
.filter(org_policies::org_uuid.eq(&self.org_uuid))
.filter(org_policies::atype.eq(&self.atype)),
)
.execute(&**conn)
.map_res("Error deleting org_policy for insert")?;
diesel::insert_into(org_policies::table)
.values(self)
.on_conflict(org_policies::uuid)
.do_update()
.set(self)
.execute(&**conn)
.map_res("Error saving org_policy")
}
#[cfg(not(feature = "postgresql"))]
pub fn save(&self, conn: &DbConn) -> EmptyResult {
diesel::replace_into(org_policies::table)
.values(&*self)
.execute(&**conn)
.map_res("Error saving org_policy")
}
pub fn delete(self, conn: &DbConn) -> EmptyResult {
diesel::delete(org_policies::table.filter(org_policies::uuid.eq(self.uuid)))
.execute(&**conn)
.map_res("Error deleting org_policy")
}
pub fn find_by_uuid(uuid: &str, conn: &DbConn) -> Option<Self> {
org_policies::table
.filter(org_policies::uuid.eq(uuid))
.first::<Self>(&**conn)
.ok()
}
pub fn find_by_org(org_uuid: &str, conn: &DbConn) -> Vec<Self> {
org_policies::table
.filter(org_policies::org_uuid.eq(org_uuid))
.load::<Self>(&**conn)
.expect("Error loading org_policy")
}
pub fn find_by_user(user_uuid: &str, conn: &DbConn) -> Vec<Self> {
use crate::db::schema::users_organizations;
org_policies::table
.left_join(
users_organizations::table.on(
users_organizations::org_uuid.eq(org_policies::org_uuid)
.and(users_organizations::user_uuid.eq(user_uuid)))
)
.select(org_policies::all_columns)
.load::<Self>(&**conn)
.expect("Error loading org_policy")
}
pub fn find_by_org_and_type(org_uuid: &str, atype: i32, conn: &DbConn) -> Option<Self> {
org_policies::table
.filter(org_policies::org_uuid.eq(org_uuid))
.filter(org_policies::atype.eq(atype))
.first::<Self>(&**conn)
.ok()
}
pub fn delete_all_by_organization(org_uuid: &str, conn: &DbConn) -> EmptyResult {
diesel::delete(org_policies::table.filter(org_policies::org_uuid.eq(org_uuid)))
.execute(&**conn)
.map_res("Error deleting org_policy")
}
/*pub fn delete_all_by_user(user_uuid: &str, conn: &DbConn) -> EmptyResult {
diesel::delete(twofactor::table.filter(twofactor::user_uuid.eq(user_uuid)))
.execute(&**conn)
.map_res("Error deleting twofactors")
}*/
}

View File

@@ -1,7 +1,8 @@
use serde_json::Value;
use std::cmp::Ordering;
use num_traits::FromPrimitive;
use super::{CollectionUser, User};
use super::{CollectionUser, User, OrgPolicy};
#[derive(Debug, Identifiable, Queryable, Insertable, AsChangeset)]
#[table_name = "organizations"]
@@ -33,6 +34,7 @@ pub enum UserOrgStatus {
}
#[derive(Copy, Clone, PartialEq, Eq)]
#[derive(FromPrimitive)]
pub enum UserOrgType {
Owner = 0,
Admin = 1,
@@ -135,16 +137,6 @@ impl UserOrgType {
_ => None,
}
}
pub fn from_i32(i: i32) -> Option<Self> {
match i {
0 => Some(UserOrgType::Owner),
1 => Some(UserOrgType::Admin),
2 => Some(UserOrgType::User),
3 => Some(UserOrgType::Manager),
_ => None,
}
}
}
/// Local methods
@@ -170,6 +162,7 @@ impl Organization {
"UseEvents": false,
"UseGroups": false,
"UseTotp": true,
"UsePolicies": true,
"BusinessName": null,
"BusinessAddress1": null,
@@ -250,6 +243,7 @@ impl Organization {
Cipher::delete_all_by_organization(&self.uuid, &conn)?;
Collection::delete_all_by_organization(&self.uuid, &conn)?;
UserOrganization::delete_all_by_organization(&self.uuid, &conn)?;
OrgPolicy::delete_all_by_organization(&self.uuid, &conn)?;
diesel::delete(organizations::table.filter(organizations::uuid.eq(self.uuid)))
.execute(&**conn)
@@ -280,6 +274,7 @@ impl UserOrganization {
"UseEvents": false,
"UseGroups": false,
"UseTotp": true,
"UsePolicies": true,
"MaxStorageGb": 10, // The value doesn't matter, we don't check server-side

View File

@@ -73,6 +73,13 @@ impl TwoFactor {
impl TwoFactor {
#[cfg(feature = "postgresql")]
pub fn save(&self, conn: &DbConn) -> EmptyResult {
// We need to make sure we're not going to violate the unique constraint on user_uuid and atype.
// This happens automatically on other DBMS backends due to replace_into(). PostgreSQL does
// not support multiple constraints on ON CONFLICT clauses.
diesel::delete(twofactor::table.filter(twofactor::user_uuid.eq(&self.user_uuid)).filter(twofactor::atype.eq(&self.atype)))
.execute(&**conn)
.map_res("Error deleting twofactor for insert")?;
diesel::insert_into(twofactor::table)
.values(self)
.on_conflict(twofactor::uuid)

View File

@@ -319,8 +319,7 @@ impl Invitation {
}
pub fn take(mail: &str, conn: &DbConn) -> bool {
CONFIG.invitations_allowed()
&& match Self::find_by_mail(mail, &conn) {
match Self::find_by_mail(mail, &conn) {
Some(invitation) => invitation.delete(&conn).is_ok(),
None => false,
}

View File

@@ -77,6 +77,16 @@ table! {
}
}
table! {
org_policies (uuid) {
uuid -> Varchar,
org_uuid -> Varchar,
atype -> Integer,
enabled -> Bool,
data -> Text,
}
}
table! {
organizations (uuid) {
uuid -> Varchar,
@@ -155,6 +165,7 @@ joinable!(devices -> users (user_uuid));
joinable!(folders -> users (user_uuid));
joinable!(folders_ciphers -> ciphers (cipher_uuid));
joinable!(folders_ciphers -> folders (folder_uuid));
joinable!(org_policies -> organizations (org_uuid));
joinable!(twofactor -> users (user_uuid));
joinable!(users_collections -> collections (collection_uuid));
joinable!(users_collections -> users (user_uuid));
@@ -170,6 +181,7 @@ allow_tables_to_appear_in_same_query!(
folders,
folders_ciphers,
invitations,
org_policies,
organizations,
twofactor,
users,

View File

@@ -77,6 +77,16 @@ table! {
}
}
table! {
org_policies (uuid) {
uuid -> Text,
org_uuid -> Text,
atype -> Integer,
enabled -> Bool,
data -> Text,
}
}
table! {
organizations (uuid) {
uuid -> Text,
@@ -155,6 +165,7 @@ joinable!(devices -> users (user_uuid));
joinable!(folders -> users (user_uuid));
joinable!(folders_ciphers -> ciphers (cipher_uuid));
joinable!(folders_ciphers -> folders (folder_uuid));
joinable!(org_policies -> organizations (org_uuid));
joinable!(twofactor -> users (user_uuid));
joinable!(users_collections -> collections (collection_uuid));
joinable!(users_collections -> users (user_uuid));
@@ -170,6 +181,7 @@ allow_tables_to_appear_in_same_query!(
folders,
folders_ciphers,
invitations,
org_policies,
organizations,
twofactor,
users,

View File

@@ -77,6 +77,16 @@ table! {
}
}
table! {
org_policies (uuid) {
uuid -> Text,
org_uuid -> Text,
atype -> Integer,
enabled -> Bool,
data -> Text,
}
}
table! {
organizations (uuid) {
uuid -> Text,
@@ -155,6 +165,7 @@ joinable!(devices -> users (user_uuid));
joinable!(folders -> users (user_uuid));
joinable!(folders_ciphers -> ciphers (cipher_uuid));
joinable!(folders_ciphers -> folders (folder_uuid));
joinable!(org_policies -> organizations (org_uuid));
joinable!(twofactor -> users (user_uuid));
joinable!(users_collections -> collections (collection_uuid));
joinable!(users_collections -> users (user_uuid));
@@ -170,6 +181,7 @@ allow_tables_to_appear_in_same_query!(
folders,
folders_ciphers,
invitations,
org_policies,
organizations,
twofactor,
users,

View File

@@ -46,6 +46,7 @@ use std::option::NoneError as NoneErr;
use std::time::SystemTimeError as TimeErr;
use u2f::u2ferror::U2fError as U2fErr;
use yubico::yubicoerror::YubicoError as YubiErr;
use lettre::smtp::error::Error as LettreErr;
#[derive(Display, Serialize)]
pub struct Empty {}
@@ -73,6 +74,7 @@ make_error! {
ReqError(ReqErr): _has_source, _api_error,
RegexError(RegexErr): _has_source, _api_error,
YubiError(YubiErr): _has_source, _api_error,
LetreErr(LettreErr): _has_source, _api_error,
}
// This is implemented by hand because NoneError doesn't implement neither Display nor Error
@@ -209,6 +211,18 @@ macro_rules! err {
}};
}
#[macro_export]
macro_rules! err_discard {
($msg:expr, $data:expr) => {{
std::io::copy(&mut $data.open(), &mut std::io::sink()).ok();
return Err(crate::error::Error::new($msg, $msg));
}};
($usr_msg:expr, $log_value:expr, $data:expr) => {{
std::io::copy(&mut $data.open(), &mut std::io::sink()).ok();
return Err(crate::error::Error::new($usr_msg, $log_value));
}};
}
#[macro_export]
macro_rules! err_json {
($expr:expr, $log_value:expr) => {{

View File

@@ -1,8 +1,10 @@
use lettre::smtp::authentication::Credentials;
use lettre::smtp::authentication::Mechanism as SmtpAuthMechanism;
use lettre::smtp::ConnectionReuseParameters;
use lettre::{ClientSecurity, ClientTlsParameters, SmtpClient, SmtpTransport, Transport};
use lettre_email::{EmailBuilder, MimeMultipartType, PartBuilder};
use lettre::{
builder::{EmailBuilder, MimeMultipartType, PartBuilder},
ClientSecurity, ClientTlsParameters, SmtpClient, SmtpTransport, Transport,
};
use native_tls::{Protocol, TlsConnector};
use percent_encoding::{percent_encode, NON_ALPHANUMERIC};
use quoted_printable::encode_to_str;
@@ -42,10 +44,11 @@ fn mailer() -> SmtpTransport {
_ => smtp_client,
};
let smtp_client = match &CONFIG.smtp_auth_mechanism() {
Some(auth_mechanism_json) => {
let auth_mechanism = serde_json::from_str::<SmtpAuthMechanism>(&auth_mechanism_json);
match auth_mechanism {
let smtp_client = match CONFIG.smtp_auth_mechanism() {
Some(mechanism) => {
let correct_mechanism = format!("\"{}\"", crate::util::upcase_first(&mechanism.trim_matches('"')));
match serde_json::from_str::<SmtpAuthMechanism>(&correct_mechanism) {
Ok(auth_mechanism) => smtp_client.authentication_mechanism(auth_mechanism),
_ => panic!("Failure to parse mechanism. Is it proper Json? Eg. `\"Plain\"` not `Plain`"),
}
@@ -256,7 +259,30 @@ pub fn send_change_email(address: &str, token: &str) -> EmptyResult {
send_email(&address, &subject, &body_html, &body_text)
}
pub fn send_test(address: &str) -> EmptyResult {
let (subject, body_html, body_text) = get_text(
"email/smtp_test",
json!({
"url": CONFIG.domain(),
}),
)?;
send_email(&address, &subject, &body_html, &body_text)
}
fn send_email(address: &str, subject: &str, body_html: &str, body_text: &str) -> 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 = PartBuilder::new()
.body(encode_to_str(body_html))
.header(("Content-Type", "text/html; charset=utf-8"))
@@ -284,12 +310,11 @@ fn send_email(address: &str, subject: &str, body_html: &str, body_text: &str) ->
let mut transport = mailer();
let result = transport
.send(email.into())
.map_err(|e| Error::new("Error sending email", e.to_string()))
.and(Ok(()));
let result = transport.send(email);
// Explicitly close the connection, in case of error
transport.close();
result
result?;
Ok(())
}

View File

@@ -1,7 +1,6 @@
#![feature(proc_macro_hygiene, vec_remove_item, try_trait, ip)]
#![recursion_limit = "256"]
#[cfg(feature = "openssl")]
extern crate openssl;
#[macro_use]
extern crate rocket;
@@ -16,17 +15,18 @@ extern crate diesel;
#[macro_use]
extern crate diesel_migrations;
#[macro_use]
extern crate lazy_static;
#[macro_use]
extern crate derive_more;
#[macro_use]
extern crate num_derive;
extern crate backtrace;
use std::{
fs::create_dir_all,
path::Path,
process::{exit, Command},
str::FromStr,
panic, thread, fmt // For panic logging
};
#[macro_use]
@@ -42,7 +42,28 @@ mod util;
pub use config::CONFIG;
pub use error::{Error, MapResult};
use structopt::StructOpt;
// Used for catching panics and log them to file instead of stderr
use backtrace::Backtrace;
struct Shim(Backtrace);
impl fmt::Debug for Shim {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
write!(fmt, "\n{:?}", self.0)
}
}
#[derive(Debug, StructOpt)]
#[structopt(name = "bitwarden_rs", about = "A Bitwarden API server written in Rust")]
struct Opt {
/// Prints the app version
#[structopt(short, long)]
version: bool,
}
fn main() {
parse_args();
launch_info();
use log::LevelFilter as LF;
@@ -64,11 +85,23 @@ fn main() {
launch_rocket(extra_debug);
}
fn parse_args() {
let opt = Opt::from_args();
if opt.version {
if let Some(version) = option_env!("BWRS_VERSION") {
println!("bitwarden_rs {}", version);
} else {
println!("bitwarden_rs (Version info from Git not present)");
}
exit(0);
}
}
fn launch_info() {
println!("/--------------------------------------------------------------------\\");
println!("| Starting Bitwarden_RS |");
if let Some(version) = option_env!("GIT_VERSION") {
if let Some(version) = option_env!("BWRS_VERSION") {
println!("|{:^68}|", format!("Version {}", version));
}
@@ -121,6 +154,44 @@ fn init_logging(level: log::LevelFilter) -> Result<(), fern::InitError> {
logger.apply()?;
// Catch panics and log them instead of default output to StdErr
panic::set_hook(Box::new(|info| {
let backtrace = Backtrace::new();
let thread = thread::current();
let thread = thread.name().unwrap_or("unnamed");
let msg = match info.payload().downcast_ref::<&'static str>() {
Some(s) => *s,
None => match info.payload().downcast_ref::<String>() {
Some(s) => &**s,
None => "Box<Any>",
},
};
match info.location() {
Some(location) => {
error!(
target: "panic", "thread '{}' panicked at '{}': {}:{}{:?}",
thread,
msg,
location.file(),
location.line(),
Shim(backtrace)
);
}
None => {
error!(
target: "panic",
"thread '{}' panicked at '{}'{:?}",
thread,
msg,
Shim(backtrace)
)
}
}
}));
Ok(())
}
@@ -177,7 +248,9 @@ fn check_rsa_keys() {
info!("JWT keys don't exist, checking if OpenSSL is available...");
Command::new("openssl").arg("version").status().unwrap_or_else(|_| {
info!("Can't create keys because OpenSSL is not available, make sure it's installed and available on the PATH");
info!(
"Can't create keys because OpenSSL is not available, make sure it's installed and available on the PATH"
);
exit(1);
});
@@ -250,23 +323,35 @@ mod migrations {
let connection = crate::db::get_connection().expect("Can't connect to DB");
use std::io::stdout;
// Disable Foreign Key Checks during migration
use diesel::RunQueryDsl;
#[cfg(feature = "postgres")]
diesel::sql_query("SET CONSTRAINTS ALL DEFERRED").execute(&connection).expect("Failed to disable Foreign Key Checks during migrations");
#[cfg(feature = "mysql")]
diesel::sql_query("SET FOREIGN_KEY_CHECKS = 0").execute(&connection).expect("Failed to disable Foreign Key Checks during migrations");
#[cfg(feature = "sqlite")]
diesel::sql_query("PRAGMA defer_foreign_keys = ON").execute(&connection).expect("Failed to disable Foreign Key Checks during migrations");
embedded_migrations::run_with_output(&connection, &mut stdout()).expect("Can't run migrations");
}
}
fn launch_rocket(extra_debug: bool) {
// Create Rocket object, this stores current log level and sets it's own
// Create Rocket object, this stores current log level and sets its own
let rocket = rocket::ignite();
// If addding more base paths here, consider also adding them to
let basepath = &CONFIG.domain_path();
// If adding more paths here, consider also adding them to
// crate::utils::LOGGED_ROUTES to make sure they appear in the log
let rocket = rocket
.mount("/", api::web_routes())
.mount("/api", api::core_routes())
.mount("/admin", api::admin_routes())
.mount("/identity", api::identity_routes())
.mount("/icons", api::icons_routes())
.mount("/notifications", api::notifications_routes())
.mount(&[basepath, "/"].concat(), api::web_routes())
.mount(&[basepath, "/api"].concat(), api::core_routes())
.mount(&[basepath, "/admin"].concat(), api::admin_routes())
.mount(&[basepath, "/identity"].concat(), api::identity_routes())
.mount(&[basepath, "/icons"].concat(), api::icons_routes())
.mount(&[basepath, "/notifications"].concat(), api::notifications_routes())
.manage(db::init_pool())
.manage(api::start_notification_server())
.attach(util::AppHeaders())

View File

@@ -775,5 +775,71 @@
"customercontrolpanel.de"
],
"Excluded": false
},
{
"Type": 76,
"Domains": [
"docusign.com",
"docusign.net"
],
"Excluded": false
},
{
"Type": 77,
"Domains": [
"envato.com",
"themeforest.net",
"codecanyon.net",
"videohive.net",
"audiojungle.net",
"graphicriver.net",
"photodune.net",
"3docean.net"
],
"Excluded": false
},
{
"Type": 78,
"Domains": [
"x10hosting.com",
"x10premium.com"
],
"Excluded": false
},
{
"Type": 79,
"Domains": [
"dnsomatic.com",
"opendns.com",
"umbrella.com"
],
"Excluded": false
},
{
"Type": 80,
"Domains": [
"cagreatamerica.com",
"canadaswonderland.com",
"carowinds.com",
"cedarfair.com",
"cedarpoint.com",
"dorneypark.com",
"kingsdominion.com",
"knotts.com",
"miadventure.com",
"schlitterbahn.com",
"valleyfair.com",
"visitkingsisland.com",
"worldsoffun.com"
],
"Excluded": false
},
{
"Type": 81,
"Domains": [
"ubnt.com",
"ui.com"
],
"Excluded": false
}
]

View File

@@ -6,10 +6,10 @@
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Bitwarden_rs Admin Panel</title>
<link rel="stylesheet" href="/bwrs_static/bootstrap.css" />
<script src="/bwrs_static/bootstrap-native-v4.js"></script>
<script src="/bwrs_static/md5.js"></script>
<script src="/bwrs_static/identicon.js"></script>
<link rel="stylesheet" href="{{urlpath}}/bwrs_static/bootstrap.css" />
<script src="{{urlpath}}/bwrs_static/bootstrap-native-v4.js"></script>
<script src="{{urlpath}}/bwrs_static/md5.js"></script>
<script src="{{urlpath}}/bwrs_static/identicon.js"></script>
<style>
body {
padding-top: 70px;
@@ -38,10 +38,10 @@
<div class="navbar-collapse">
<ul class="navbar-nav">
<li class="nav-item active">
<a class="nav-link" href="/admin">Admin Panel</a>
<a class="nav-link" href="{{urlpath}}/admin">Admin Panel</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/">Vault</a>
<a class="nav-link" href="{{urlpath}}/">Vault</a>
</li>
</ul>
</div>
@@ -55,7 +55,7 @@
{{#if logged_in}}
<li class="nav-item">
<a class="nav-link" href="/admin/logout">Log Out</a>
<a class="nav-link" href="{{urlpath}}/admin/logout">Log Out</a>
</li>
{{/if}}
</ul>

View File

@@ -71,6 +71,7 @@
them to avoid confusion. This does not apply to the read-only section, which can only be set through the
environment.
</div>
<form class="form accordion" id="config-form" onsubmit="saveConfig(); return false;">
{{#each config}}
{{#if groupdoc}}
@@ -110,6 +111,17 @@
</div>
{{/if}}
{{/each}}
{{#case group "smtp"}}
<div class="form-group row pt-3 border-top" title="Send a test email to given email address">
<label for="smtp-test-email" class="col-sm-3 col-form-label">Test SMTP</label>
<div class="col-sm-8 input-group">
<input class="form-control" id="smtp-test-email" type="email" placeholder="Enter test email">
<div class="input-group-append">
<button type="button" class="btn btn-outline-primary" onclick="smtpTest(); return false;">Send test email</button>
</div>
</div>
</div>
{{/case}}
</div>
</div>
{{/if}}
@@ -191,7 +203,10 @@
<script>
function reload() { window.location.reload(); }
function msg(text) { text && alert(text); reload(); }
function msg(text, reload_page = true) {
text && alert(text);
reload_page && reload();
}
function identicon(email) {
const data = new Identicon(md5(email), { size: 48, format: 'svg' });
return "data:image/svg+xml;base64," + data.toString();
@@ -206,26 +221,37 @@
}
return false;
}
function _post(url, successMsg, errMsg, body) {
function _post(url, successMsg, errMsg, body, reload_page = true) {
fetch(url, {
method: 'POST',
body: body,
mode: "same-origin",
credentials: "same-origin",
headers: { "Content-Type": "application/json" }
}).then(e => {
if (e.ok) { return msg(successMsg); }
e.json().then(json => {
const msg = json ? json.ErrorModel.Message : "Unknown error";
msg(errMsg + ": " + msg);
}).then( resp => {
if (resp.ok) { msg(successMsg, reload_page); return Promise.reject({error: false}); }
respStatus = resp.status;
respStatusText = resp.statusText;
return resp.text();
}).then( respText => {
try {
const respJson = JSON.parse(respText);
return respJson ? respJson.ErrorModel.Message : "Unknown error";
} catch (e) {
return Promise.reject({body:respStatus + ' - ' + respStatusText, error: true});
}
}).then( apiMsg => {
msg(errMsg + "\n" + apiMsg, reload_page);
}).catch( e => {
if (e.error === false) { return true; }
else { msg(errMsg + "\n" + e.body, reload_page); }
});
}).catch(e => { msg(errMsg + ": Unknown error") });
}
function deleteUser(id, mail) {
var input_mail = prompt("To delete user '" + mail + "', please type the email below")
if (input_mail != null) {
if (input_mail == mail) {
_post("/admin/users/" + id + "/delete",
_post("{{urlpath}}/admin/users/" + id + "/delete",
"User deleted correctly",
"Error deleting user");
} else {
@@ -235,19 +261,19 @@
return false;
}
function remove2fa(id) {
_post("/admin/users/" + id + "/remove-2fa",
_post("{{urlpath}}/admin/users/" + id + "/remove-2fa",
"2FA removed correctly",
"Error removing 2FA");
return false;
}
function deauthUser(id) {
_post("/admin/users/" + id + "/deauth",
_post("{{urlpath}}/admin/users/" + id + "/deauth",
"Sessions deauthorized correctly",
"Error deauthorizing sessions");
return false;
}
function updateRevisions() {
_post("/admin/users/update_revision",
_post("{{urlpath}}/admin/users/update_revision",
"Success, clients will sync next time they connect",
"Error forcing clients to sync");
return false;
@@ -256,10 +282,18 @@
inv = document.getElementById("email-invite");
data = JSON.stringify({ "email": inv.value });
inv.value = "";
_post("/admin/invite/", "User invited correctly",
_post("{{urlpath}}/admin/invite/", "User invited correctly",
"Error inviting user", data);
return false;
}
function smtpTest() {
test_email = document.getElementById("smtp-test-email");
data = JSON.stringify({ "email": test_email.value });
_post("{{urlpath}}/admin/test/smtp/",
"SMTP Test email sent correctly",
"Error sending SMTP test email", data, false);
return false;
}
function getFormData() {
let data = {};
@@ -278,7 +312,7 @@
}
function saveConfig() {
data = JSON.stringify(getFormData());
_post("/admin/config/", "Config saved correctly",
_post("{{urlpath}}/admin/config/", "Config saved correctly",
"Error saving config", data);
return false;
}
@@ -286,7 +320,7 @@
var input = prompt("This will remove all user configurations, and restore the defaults and the " +
"values set by the environment. This operation could be dangerous. Type 'DELETE' to proceed:");
if (input === "DELETE") {
_post("/admin/config/delete",
_post("{{urlpath}}/admin/config/delete",
"Config deleted correctly",
"Error deleting config");
} else {
@@ -296,9 +330,9 @@
return false;
}
function backupDatabase() {
_post("/admin/config/backup_db",
_post("{{urlpath}}/admin/config/backup_db",
"Backup created successfully",
"Error creating backup");
"Error creating backup", null, false);
return false;
}
function masterCheck(check_id, inputs_query) {

View File

@@ -3,6 +3,6 @@ Invitation accepted
<html>
<p>
Your invitation for <b>{{email}}</b> to join <b>{{org_name}}</b> was accepted.
Please <a href="{{url}}">log in</a> to the bitwarden_rs server and confirm them from the organization management page.
Please <a href="{{url}}/">log in</a> to the bitwarden_rs server and confirm them from the organization management page.
</p>
</html>

View File

@@ -101,7 +101,7 @@ Invitation accepted
</tr>
<tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
<td class="content-block" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; margin: 0; -webkit-font-smoothing: antialiased; padding: 0 0 10px; -webkit-text-size-adjust: none;" valign="top">
Please <a href="{{url}}">log in</a> to the bitwarden_rs server and confirm them from the organization management page.
Please <a href="{{url}}/">log in</a> to the bitwarden_rs server and confirm them from the organization management page.
</td>
</tr>
<tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">

View File

@@ -3,6 +3,6 @@ Invitation to {{org_name}} confirmed
<html>
<p>
Your invitation to join <b>{{org_name}}</b> was confirmed.
It will now appear under the Organizations the next time you <a href="{{url}}">log in</a> to the web vault.
It will now appear under the Organizations the next time you <a href="{{url}}/">log in</a> to the web vault.
</p>
</html>

View File

@@ -102,7 +102,7 @@ Invitation to {{org_name}} confirmed
<tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
<td class="content-block last" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; margin: 0; -webkit-font-smoothing: antialiased; padding: 0; -webkit-text-size-adjust: none;" valign="top">
Any collections and logins being shared with you by this organization will now appear in your Bitwarden vault. <br>
<a href="{{url}}">Log in</a>
<a href="{{url}}/">Log in</a>
</td>
</tr>
</table>

View File

@@ -9,6 +9,6 @@ New Device Logged In From {{device}}
Device Type: {{device}}
You can deauthorize all devices that have access to your account from the
<a href="{{url}}">web vault</a> under Settings > My Account > Deauthorize Sessions.
<a href="{{url}}/">web vault</a> under Settings > My Account > Deauthorize Sessions.
</p>
</html>

View File

@@ -116,7 +116,7 @@ New Device Logged In From {{device}}
</tr>
<tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
<td class="content-block last" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; margin: 0; -webkit-font-smoothing: antialiased; padding: 0; -webkit-text-size-adjust: none;" valign="top">
You can deauthorize all devices that have access to your account from the <a href="{{url}}">web vault</a> under Settings > My Account > Deauthorize Sessions.
You can deauthorize all devices that have access to your account from the <a href="{{url}}/">web vault</a> under Settings > My Account > Deauthorize Sessions.
</td>
</tr>
</table>

View File

@@ -3,7 +3,7 @@ Your master password hint
You (or someone) recently requested your master password hint.
Your hint is: "{{hint}}"
Log in: <a href="{{url}}">Web Vault</a>
Log in: <a href="{{url}}/">Web Vault</a>
If you cannot remember your master password, there is no way to recover your data. The only option to gain access to your account again is to <a href="{{url}}/#/recover-delete">delete the account</a> so that you can register again and start over. All data associated with your account will be deleted.

View File

@@ -102,7 +102,7 @@ Your master password hint
<tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
<td class="content-block" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; margin: 0; -webkit-font-smoothing: antialiased; padding: 0 0 10px; -webkit-text-size-adjust: none;" valign="top">
Your hint is: "{{hint}}"<br style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;" />
Log in: <a href="{{url}}">Web Vault</a>
Log in: <a href="{{url}}/">Web Vault</a>
</td>
</tr>
<tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">

View File

@@ -0,0 +1,8 @@
Bitwarden_rs SMTP Test
<!---------------->
<html>
<p>
This is a test email to verify the SMTP configuration for <a href="{{url}}">{{url}}</a>.
</p>
<p>When you can read this email it is probably configured correctly.</p>
</html>

View File

@@ -0,0 +1,129 @@
Bitwarden_rs SMTP Test
<!---------------->
<html xmlns="http://www.w3.org/1999/xhtml" xmlns="http://www.w3.org/1999/xhtml" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0;">
<head>
<meta name="viewport" content="width=device-width" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Bitwarden_rs</title>
</head>
<body style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; height: 100%; line-height: 25px; width: 100% !important;" bgcolor="#f6f6f6">
<style type="text/css">
 body {
margin: 0;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
box-sizing: border-box;
font-size: 16px;
color: #333;
line-height: 25px;
-webkit-font-smoothing: antialiased;
-webkit-text-size-adjust: none;
}
body * {
margin: 0;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
box-sizing: border-box;
font-size: 16px;
color: #333;
line-height: 25px;
-webkit-font-smoothing: antialiased;
-webkit-text-size-adjust: none;
}
img {
max-width: 100%;
border: none;
}
body {
-webkit-font-smoothing: antialiased;
-webkit-text-size-adjust: none;
width: 100% !important;
height: 100%;
line-height: 25px;
}
body {
background-color: #f6f6f6;
}
@media only screen and (max-width: 600px) {
body {
padding: 0 !important;
}
.container {
padding: 0 !important;
width: 100% !important;
}
.container-table {
padding: 0 !important;
width: 100% !important;
}
.content {
padding: 0 0 10px 0 !important;
}
.content-wrap {
padding: 10px !important;
}
.invoice {
width: 100% !important;
}
.main {
border-right: none !important;
border-left: none !important;
border-radius: 0 !important;
}
.logo {
padding-top: 10px !important;
}
.footer {
margin-top: 10px !important;
}
.indented {
padding-left: 10px;
}
}
</style>
<table class="body-wrap" cellpadding="0" cellspacing="0" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0; width: 100%;" bgcolor="#f6f6f6">
<tr style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0;">
<td valign="middle" class="aligncenter middle logo" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0; padding: 20px 0 10px;" align="center">
<img src="{{url}}/bwrs_static/logo-gray.png" alt="" width="250" height="39" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; border: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0; max-width: 100%;" />
</td>
</tr>
<tr style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0;">
<td class="container" align="center" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; clear: both !important; color: #333; display: block !important; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0 auto; max-width: 600px !important; width: 600px;" valign="top">
<table cellpadding="0" cellspacing="0" class="container-table" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; clear: both !important; color: #333; display: block !important; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0 auto; max-width: 600px !important; width: 600px;">
<tr style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0;">
<td class="content" align="center" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; display: block; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 0; line-height: 0; margin: 0 auto; max-width: 600px; padding-bottom: 20px;" valign="top">
<table class="main" width="100%" cellpadding="0" cellspacing="0" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; margin: 0; -webkit-text-size-adjust: none; border: 1px solid #e9e9e9; border-radius: 3px;" bgcolor="white">
<tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
<td class="content-wrap" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; margin: 0; -webkit-font-smoothing: antialiased; padding: 20px; -webkit-text-size-adjust: none;" valign="top">
<table width="100%" cellpadding="0" cellspacing="0" style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
<tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
<td class="content-block" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; margin: 0; -webkit-font-smoothing: antialiased; padding: 0 0 10px; -webkit-text-size-adjust: none; text-align: center;" valign="top" align="center">
This is a test email to verify the SMTP configuration for <a href="{{url}}">{{url}}</a>.
</td>
</tr>
<tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
<td class="content-block last" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; margin: 0; -webkit-font-smoothing: antialiased; padding: 0; -webkit-text-size-adjust: none; text-align: center;" valign="top" align="center">
When you can read this email it is probably configured correctly.
</td>
</tr>
</table>
</td>
</tr>
</table>
<table class="footer" cellpadding="0" cellspacing="0" width="100%" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; clear: both; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; width: 100%;">
<tr style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0;">
<td class="aligncenter social-icons" align="center" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; padding: 15px 0 0 0;" valign="top">
<table cellpadding="0" cellspacing="0" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0 auto;">
<tr style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0;">
<td style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; padding: 0 10px;" valign="top"><a href="https://github.com/dani-garcia/bitwarden_rs" target="_blank" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; text-decoration: underline;"><img src="{{url}}/bwrs_static/mail-github.png" alt="GitHub" width="30" height="30" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; border: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0; max-width: 100%;" /></a></td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>

View File

@@ -2,7 +2,7 @@ Welcome
<!---------------->
<html>
<p>
Thank you for creating an account at <a href="{{url}}">{{url}}</a>. You may now log in with your new account.
Thank you for creating an account at <a href="{{url}}/">{{url}}</a>. You may now log in with your new account.
</p>
<p>If you did not request to create an account, you can safely ignore this email.</p>
</html>

View File

@@ -96,7 +96,7 @@ Welcome
<table width="100%" cellpadding="0" cellspacing="0" style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
<tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
<td class="content-block" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; margin: 0; -webkit-font-smoothing: antialiased; padding: 0 0 10px; -webkit-text-size-adjust: none; text-align: center;" valign="top" align="center">
Thank you for creating an account at <a href="{{url}}">{{url}}</a>. You may now log in with your new account.
Thank you for creating an account at <a href="{{url}}/">{{url}}</a>. You may now log in with your new account.
</td>
</tr>
<tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">

View File

@@ -2,7 +2,7 @@ Welcome
<!---------------->
<html>
<p>
Thank you for creating an account at <a href="{{url}}">{{url}}</a>. Before you can login with your new account, you must verify this email address by clicking the link below.
Thank you for creating an account at <a href="{{url}}/">{{url}}</a>. Before you can login with your new account, you must verify this email address by clicking the link below.
<br>
<br>
<a href="{{url}}/#/verify-email/?userId={{user_id}}&token={{token}}">

View File

@@ -96,7 +96,7 @@ Welcome
<table width="100%" cellpadding="0" cellspacing="0" style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
<tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
<td class="content-block" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; margin: 0; -webkit-font-smoothing: antialiased; padding: 0 0 10px; -webkit-text-size-adjust: none; text-align: center;" valign="top" align="center">
Thank you for creating an account at <a href="{{url}}">{{url}}</a>. Before you can login with your new account, you must verify this email address by clicking the link below.
Thank you for creating an account at <a href="{{url}}/">{{url}}</a>. Before you can login with your new account, you must verify this email address by clicking the link below.
</td>
</tr>
<tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">

View File

@@ -7,6 +7,8 @@ use rocket::response::{self, Responder};
use rocket::{Data, Request, Response, Rocket};
use std::io::Cursor;
use crate::CONFIG;
pub struct AppHeaders();
impl Fairing for AppHeaders {
@@ -23,7 +25,7 @@ impl Fairing for AppHeaders {
res.set_raw_header("X-Frame-Options", "SAMEORIGIN");
res.set_raw_header("X-Content-Type-Options", "nosniff");
res.set_raw_header("X-XSS-Protection", "1; mode=block");
let csp = "frame-ancestors 'self' chrome-extension://nngceckbapebfimnlniiiahkandclblb moz-extension://*;";
let csp = format!("frame-ancestors 'self' chrome-extension://nngceckbapebfimnlniiiahkandclblb moz-extension://* {};", CONFIG.allowed_iframe_ancestors());
res.set_raw_header("Content-Security-Policy", csp);
// Disable cache unless otherwise specified
@@ -107,7 +109,7 @@ impl<'r, R: Responder<'r>> Responder<'r> for Cached<R> {
}
}
// Log all the routes from the main base paths list, and the attachments endoint
// Log all the routes from the main paths list, and the attachments endpoint
// Effectively ignores, any static file route, and the alive endpoint
const LOGGED_ROUTES: [&str; 6] = [
"/api",
@@ -131,7 +133,9 @@ impl Fairing for BetterLogging {
fn on_launch(&self, rocket: &Rocket) {
if self.0 {
info!(target: "routes", "Routes loaded:");
for route in rocket.routes() {
let mut routes: Vec<_> = rocket.routes().collect();
routes.sort_by_key(|r| r.uri.path());
for route in routes {
if route.rank < 0 {
info!(target: "routes", "{:<6} {}", route.method, route.uri);
} else {
@@ -153,7 +157,10 @@ impl Fairing for BetterLogging {
}
let uri = request.uri();
let uri_path = uri.path();
if self.0 || LOGGED_ROUTES.iter().any(|r| uri_path.starts_with(r)) {
// FIXME: trim_start_matches() could result in over-trimming in pathological cases;
// strip_prefix() would be a better option once it's stable.
let uri_subpath = uri_path.trim_start_matches(&CONFIG.domain_path());
if self.0 || LOGGED_ROUTES.iter().any(|r| uri_subpath.starts_with(r)) {
match uri.query() {
Some(q) => info!(target: "request", "{} {}?{}", method, uri_path, &q[..q.len().min(30)]),
None => info!(target: "request", "{} {}", method, uri_path),
@@ -165,8 +172,10 @@ impl Fairing for BetterLogging {
if !self.0 && request.method() == Method::Options {
return;
}
let uri_path = request.uri().path();
if self.0 || LOGGED_ROUTES.iter().any(|r| uri_path.starts_with(r)) {
// FIXME: trim_start_matches() could result in over-trimming in pathological cases;
// strip_prefix() would be a better option once it's stable.
let uri_subpath = request.uri().path().trim_start_matches(&CONFIG.domain_path());
if self.0 || LOGGED_ROUTES.iter().any(|r| uri_subpath.starts_with(r)) {
let status = response.status();
if let Some(ref route) = request.route() {
info!(target: "response", "{} => {} {}", route, status.code, status.reason)
@@ -309,6 +318,17 @@ where
try_parse_string(env::var(key))
}
const TRUE_VALUES: &[&str] = &["true", "t", "yes", "y", "1"];
const FALSE_VALUES: &[&str] = &["false", "f", "no", "n", "0"];
pub fn get_env_bool(key: &str) -> Option<bool> {
match env::var(key) {
Ok(val) if TRUE_VALUES.contains(&val.to_lowercase().as_ref()) => Some(true),
Ok(val) if FALSE_VALUES.contains(&val.to_lowercase().as_ref()) => Some(false),
_ => None,
}
}
//
// Date util methods
//