Merge branch 'master' of https://github.com/dani-garcia/bitwarden_rs into log-panics

This commit is contained in:
BlackDex 2020-03-16 17:19:27 +01:00
commit bcbe6177b8
63 changed files with 1683 additions and 627 deletions

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. 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. 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

934
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -26,7 +26,8 @@ rocket = { version = "0.5.0-dev", features = ["tls"], default-features = false }
rocket_contrib = "0.5.0-dev" rocket_contrib = "0.5.0-dev"
# HTTP client # HTTP client
reqwest = "0.9.24" # reqwest = "0.9.24"
reqwest = { version = "0.10.4", features = ["blocking", "json"] }
# multipart/form-data support # multipart/form-data support
multipart = { version = "0.16.1", features = ["server"], default-features = false } multipart = { version = "0.16.1", features = ["server"], default-features = false }
@ -35,7 +36,7 @@ multipart = { version = "0.16.1", features = ["server"], default-features = fals
ws = "0.9.1" ws = "0.9.1"
# MessagePack library # MessagePack library
rmpv = "0.4.3" rmpv = "0.4.4"
# Concurrent hashmap implementation # Concurrent hashmap implementation
chashmap = "2.2.2" chashmap = "2.2.2"
@ -43,11 +44,11 @@ chashmap = "2.2.2"
# A generic serialization/deserialization framework # A generic serialization/deserialization framework
serde = "1.0.104" serde = "1.0.104"
serde_derive = "1.0.104" serde_derive = "1.0.104"
serde_json = "1.0.45" serde_json = "1.0.48"
# Logging # Logging
log = "0.4.8" 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 # A safe, extensible ORM and Query builder
diesel = { version = "1.4.3", features = [ "chrono", "r2d2"] } diesel = { version = "1.4.3", features = [ "chrono", "r2d2"] }
@ -63,13 +64,13 @@ ring = "0.14.6"
uuid = { version = "0.8.1", features = ["v4"] } uuid = { version = "0.8.1", features = ["v4"] }
# Date and time library for Rust # Date and time library for Rust
chrono = "0.4.10" chrono = "0.4.11"
# TOTP library # TOTP library
oath = "0.10.2" oath = "0.10.2"
# Data encoding library # Data encoding library
data-encoding = "2.1.2" data-encoding = "2.2.0"
# JWT library # JWT library
jsonwebtoken = "6.0.1" jsonwebtoken = "6.0.1"
@ -83,11 +84,11 @@ yubico = { version = "0.7.1", features = ["online-tokio"], default-features = fa
# A `dotenv` implementation for Rust # A `dotenv` implementation for Rust
dotenv = { version = "0.15.0", default-features = false } dotenv = { version = "0.15.0", default-features = false }
# Lazy static macro # Lazy initialization
lazy_static = "1.4.0" once_cell = "1.3.1"
# More derives # More derives
derive_more = "0.99.2" derive_more = "0.99.3"
# Numerical libraries # Numerical libraries
num-traits = "0.2.11" num-traits = "0.2.11"
@ -95,25 +96,28 @@ num-derive = "0.3.0"
# Email libraries # Email libraries
lettre = "0.10.0-pre" lettre = "0.10.0-pre"
native-tls = "0.2.3" native-tls = "0.2.4"
quoted_printable = "0.4.1" quoted_printable = "0.4.2"
# Template library # Template library
handlebars = { version = "3.0.1", features = ["dir_source"] } handlebars = { version = "3.0.1", features = ["dir_source"] }
# For favicon extraction from main website # For favicon extraction from main website
soup = "0.4.1" soup = "0.5.0"
regex = "1.3.3" regex = "1.3.4"
data-url = "0.1.0" data-url = "0.1.0"
# Used by U2F, JWT and Postgres # Used by U2F, JWT and Postgres
openssl = "0.10.26" openssl = "0.10.28"
# URL encoding library # URL encoding library
percent-encoding = "2.1.0" percent-encoding = "2.1.0"
# Punycode conversion # Punycode conversion
idna = "0.2.0" idna = "0.2.0"
# CLI argument parsing
structopt = "0.3.11"
[patch.crates-io] [patch.crates-io]
# Use newest ring # Use newest ring
rocket = { git = 'https://github.com/SergioBenitez/Rocket', rev = 'b95b6765e1cc8be7c1e7eaef8a9d9ad940b0ac13' } rocket = { git = 'https://github.com/SergioBenitez/Rocket', rev = 'b95b6765e1cc8be7c1e7eaef8a9d9ad940b0ac13' }

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.** **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: Basically full implementation of Bitwarden API is provided including:
* Basic single user functionality * Single user functionality
* Organizations support * Organizations support
* Attachments * Attachments
* Vault API support * Vault API support
* Serving the static files for Vault interface * Serving the static files for Vault interface
* Website icons API * Website icons API
* Authenticator and U2F support * Authenticator and U2F support
* YubiKey OTP * YubiKey and Duo support
## Installation ## Installation
Pull the docker image and mount a volume from the host for persistent storage: Pull the docker image and mount a volume from the host for persistent storage:
@ -49,13 +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. 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 ## 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! 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 ### Sponsors
Thanks for your contribution to the project! Thanks for your contribution to the project!
- [@Skaronator](https://github.com/Skaronator)
- [@ChonoN](https://github.com/ChonoN) - [@ChonoN](https://github.com/ChonoN)

View file

@ -1,16 +1,10 @@
{{ "# This file was generated using a Jinja2 template." }} # This file was generated using a Jinja2 template.
{{ "# Please make your changes in `Dockerfile.j2` and then `make` the individual Dockerfile's." }} # 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 #######################
{% set build_stage_base_image = "rust:1.40" %} {% set build_stage_base_image = "rust:1.40" %}
{% set vault_stage_base_image = build_stage_base_image %}
{% if "alpine" in target_file %} {% if "alpine" in target_file %}
{% set build_stage_base_image = "clux/muslrust:nightly-2019-12-19" %} {% set build_stage_base_image = "clux/muslrust:nightly-2020-03-09" %}
{% set runtime_stage_base_image = "alpine:3.11" %} {% set runtime_stage_base_image = "alpine:3.11" %}
{% set vault_stage_base_image = runtime_stage_base_image %}
{% set package_arch_name = "" %} {% set package_arch_name = "" %}
{% elif "amd64" in target_file %} {% elif "amd64" in target_file %}
{% set runtime_stage_base_image = "debian:buster-slim" %} {% set runtime_stage_base_image = "debian:buster-slim" %}
@ -29,30 +23,23 @@
{% if package_arch_name == "" %} {% if package_arch_name == "" %}
{% set package_arch_prefix = "" %} {% set package_arch_prefix = "" %}
{% endif %} {% endif %}
FROM {{ vault_stage_base_image }} as vault # Using multistage build:
# https://docs.docker.com/develop/develop-images/multistage-build/
ENV VAULT_VERSION "v2.12.0c" # https://whitfin.io/speeding-up-rust-docker-builds/
####################### VAULT BUILD IMAGE #######################
ENV URL "https://github.com/dani-garcia/bw_web_builds/releases/download/$VAULT_VERSION/bw_web_$VAULT_VERSION.tar.gz" {% set vault_image_hash = "sha256:feb3f46d15738191b9043be4cdb1be2c0078ed411e7b7be73a2f4fcbca01e13c" %}
{% raw %}
{% if "alpine" in vault_stage_base_image %} # This hash is extracted from the docker web-vault builds and it's prefered over a simple tag because it's immutable.
RUN apk add --no-cache --upgrade curl tar # It can be viewed in multiple ways:
{% else %} # - From the https://hub.docker.com/repository/docker/bitwardenrs/web-vault/tags page, click the tag name and the digest should be there.
# Build time options to avoid dpkg warnings and help with reproducible builds. # - From the console, with the following commands:
ENV DEBIAN_FRONTEND=noninteractive LANG=C.UTF-8 TZ=UTC TERM=xterm-256color # docker pull bitwardenrs/web-vault:v2.12.0e
{% endif %} # docker image inspect --format "{{.RepoDigests}}" bitwardenrs/web-vault:v2.12.0e
#
RUN mkdir /web-vault # - To do the opposite, and get the tag from the hash, you can do:
WORKDIR /web-vault # docker image inspect --format "{{.RepoTags}}" bitwardenrs/web-vault@sha256:feb3f46d15738191b9043be4cdb1be2c0078ed411e7b7be73a2f4fcbca01e13c
{% endraw %}
{% if "alpine" in vault_stage_base_image %} FROM bitwardenrs/web-vault@{{ vault_image_hash }} as vault
SHELL ["/bin/ash", "-o", "nounset", "-o", "pipefail", "-o", "errexit", "-c"]
{% else %}
SHELL ["/bin/bash", "-o", "nounset", "-o", "pipefail", "-o", "errexit", "-c"]
{% endif %}
RUN curl -L $URL | tar xz
RUN ls
########################## BUILD IMAGE ########################## ########################## BUILD IMAGE ##########################
{% if "musl" in build_stage_base_image %} {% if "musl" in build_stage_base_image %}

View file

@ -5,22 +5,17 @@
# https://docs.docker.com/develop/develop-images/multistage-build/ # https://docs.docker.com/develop/develop-images/multistage-build/
# https://whitfin.io/speeding-up-rust-docker-builds/ # https://whitfin.io/speeding-up-rust-docker-builds/
####################### VAULT BUILD IMAGE ####################### ####################### VAULT BUILD IMAGE #######################
FROM rust:1.40 as vault
ENV VAULT_VERSION "v2.12.0c" # 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:
ENV URL "https://github.com/dani-garcia/bw_web_builds/releases/download/$VAULT_VERSION/bw_web_$VAULT_VERSION.tar.gz" # - 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:
# Build time options to avoid dpkg warnings and help with reproducible builds. # docker pull bitwardenrs/web-vault:v2.12.0e
ENV DEBIAN_FRONTEND=noninteractive LANG=C.UTF-8 TZ=UTC TERM=xterm-256color # docker image inspect --format "{{.RepoDigests}}" bitwardenrs/web-vault:v2.12.0e
#
RUN mkdir /web-vault # - To do the opposite, and get the tag from the hash, you can do:
WORKDIR /web-vault # docker image inspect --format "{{.RepoTags}}" bitwardenrs/web-vault@sha256:feb3f46d15738191b9043be4cdb1be2c0078ed411e7b7be73a2f4fcbca01e13c
FROM bitwardenrs/web-vault@sha256:feb3f46d15738191b9043be4cdb1be2c0078ed411e7b7be73a2f4fcbca01e13c as vault
SHELL ["/bin/bash", "-o", "nounset", "-o", "pipefail", "-o", "errexit", "-c"]
RUN curl -L $URL | tar xz
RUN ls
########################## BUILD IMAGE ########################## ########################## BUILD IMAGE ##########################
# We need to use the Rust build image, because # We need to use the Rust build image, because

View file

@ -5,22 +5,17 @@
# https://docs.docker.com/develop/develop-images/multistage-build/ # https://docs.docker.com/develop/develop-images/multistage-build/
# https://whitfin.io/speeding-up-rust-docker-builds/ # https://whitfin.io/speeding-up-rust-docker-builds/
####################### VAULT BUILD IMAGE ####################### ####################### VAULT BUILD IMAGE #######################
FROM rust:1.40 as vault
ENV VAULT_VERSION "v2.12.0c" # 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:
ENV URL "https://github.com/dani-garcia/bw_web_builds/releases/download/$VAULT_VERSION/bw_web_$VAULT_VERSION.tar.gz" # - 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:
# Build time options to avoid dpkg warnings and help with reproducible builds. # docker pull bitwardenrs/web-vault:v2.12.0e
ENV DEBIAN_FRONTEND=noninteractive LANG=C.UTF-8 TZ=UTC TERM=xterm-256color # docker image inspect --format "{{.RepoDigests}}" bitwardenrs/web-vault:v2.12.0e
#
RUN mkdir /web-vault # - To do the opposite, and get the tag from the hash, you can do:
WORKDIR /web-vault # docker image inspect --format "{{.RepoTags}}" bitwardenrs/web-vault@sha256:feb3f46d15738191b9043be4cdb1be2c0078ed411e7b7be73a2f4fcbca01e13c
FROM bitwardenrs/web-vault@sha256:feb3f46d15738191b9043be4cdb1be2c0078ed411e7b7be73a2f4fcbca01e13c as vault
SHELL ["/bin/bash", "-o", "nounset", "-o", "pipefail", "-o", "errexit", "-c"]
RUN curl -L $URL | tar xz
RUN ls
########################## BUILD IMAGE ########################## ########################## BUILD IMAGE ##########################
# We need to use the Rust build image, because # We need to use the Rust build image, because

View file

@ -5,22 +5,17 @@
# https://docs.docker.com/develop/develop-images/multistage-build/ # https://docs.docker.com/develop/develop-images/multistage-build/
# https://whitfin.io/speeding-up-rust-docker-builds/ # https://whitfin.io/speeding-up-rust-docker-builds/
####################### VAULT BUILD IMAGE ####################### ####################### VAULT BUILD IMAGE #######################
FROM rust:1.40 as vault
ENV VAULT_VERSION "v2.12.0c" # 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:
ENV URL "https://github.com/dani-garcia/bw_web_builds/releases/download/$VAULT_VERSION/bw_web_$VAULT_VERSION.tar.gz" # - 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:
# Build time options to avoid dpkg warnings and help with reproducible builds. # docker pull bitwardenrs/web-vault:v2.12.0e
ENV DEBIAN_FRONTEND=noninteractive LANG=C.UTF-8 TZ=UTC TERM=xterm-256color # docker image inspect --format "{{.RepoDigests}}" bitwardenrs/web-vault:v2.12.0e
#
RUN mkdir /web-vault # - To do the opposite, and get the tag from the hash, you can do:
WORKDIR /web-vault # docker image inspect --format "{{.RepoTags}}" bitwardenrs/web-vault@sha256:feb3f46d15738191b9043be4cdb1be2c0078ed411e7b7be73a2f4fcbca01e13c
FROM bitwardenrs/web-vault@sha256:feb3f46d15738191b9043be4cdb1be2c0078ed411e7b7be73a2f4fcbca01e13c as vault
SHELL ["/bin/bash", "-o", "nounset", "-o", "pipefail", "-o", "errexit", "-c"]
RUN curl -L $URL | tar xz
RUN ls
########################## BUILD IMAGE ########################## ########################## BUILD IMAGE ##########################
# We need to use the Rust build image, because # We need to use the Rust build image, because

View file

@ -5,21 +5,17 @@
# https://docs.docker.com/develop/develop-images/multistage-build/ # https://docs.docker.com/develop/develop-images/multistage-build/
# https://whitfin.io/speeding-up-rust-docker-builds/ # https://whitfin.io/speeding-up-rust-docker-builds/
####################### VAULT BUILD IMAGE ####################### ####################### VAULT BUILD IMAGE #######################
FROM alpine:3.11 as vault
ENV VAULT_VERSION "v2.12.0c" # 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:
ENV URL "https://github.com/dani-garcia/bw_web_builds/releases/download/$VAULT_VERSION/bw_web_$VAULT_VERSION.tar.gz" # - 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:
RUN apk add --no-cache --upgrade curl tar # docker pull bitwardenrs/web-vault:v2.12.0e
# docker image inspect --format "{{.RepoDigests}}" bitwardenrs/web-vault:v2.12.0e
RUN mkdir /web-vault #
WORKDIR /web-vault # - To do the opposite, and get the tag from the hash, you can do:
# docker image inspect --format "{{.RepoTags}}" bitwardenrs/web-vault@sha256:feb3f46d15738191b9043be4cdb1be2c0078ed411e7b7be73a2f4fcbca01e13c
SHELL ["/bin/ash", "-o", "nounset", "-o", "pipefail", "-o", "errexit", "-c"] FROM bitwardenrs/web-vault@sha256:feb3f46d15738191b9043be4cdb1be2c0078ed411e7b7be73a2f4fcbca01e13c as vault
RUN curl -L $URL | tar xz
RUN ls
########################## BUILD IMAGE ########################## ########################## BUILD IMAGE ##########################
# Musl build image for statically compiled binary # Musl build image for statically compiled binary

View file

@ -5,22 +5,17 @@
# https://docs.docker.com/develop/develop-images/multistage-build/ # https://docs.docker.com/develop/develop-images/multistage-build/
# https://whitfin.io/speeding-up-rust-docker-builds/ # https://whitfin.io/speeding-up-rust-docker-builds/
####################### VAULT BUILD IMAGE ####################### ####################### VAULT BUILD IMAGE #######################
FROM rust:1.40 as vault
ENV VAULT_VERSION "v2.12.0c" # 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:
ENV URL "https://github.com/dani-garcia/bw_web_builds/releases/download/$VAULT_VERSION/bw_web_$VAULT_VERSION.tar.gz" # - 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:
# Build time options to avoid dpkg warnings and help with reproducible builds. # docker pull bitwardenrs/web-vault:v2.12.0e
ENV DEBIAN_FRONTEND=noninteractive LANG=C.UTF-8 TZ=UTC TERM=xterm-256color # docker image inspect --format "{{.RepoDigests}}" bitwardenrs/web-vault:v2.12.0e
#
RUN mkdir /web-vault # - To do the opposite, and get the tag from the hash, you can do:
WORKDIR /web-vault # docker image inspect --format "{{.RepoTags}}" bitwardenrs/web-vault@sha256:feb3f46d15738191b9043be4cdb1be2c0078ed411e7b7be73a2f4fcbca01e13c
FROM bitwardenrs/web-vault@sha256:feb3f46d15738191b9043be4cdb1be2c0078ed411e7b7be73a2f4fcbca01e13c as vault
SHELL ["/bin/bash", "-o", "nounset", "-o", "pipefail", "-o", "errexit", "-c"]
RUN curl -L $URL | tar xz
RUN ls
########################## BUILD IMAGE ########################## ########################## BUILD IMAGE ##########################
# We need to use the Rust build image, because # We need to use the Rust build image, because

View file

@ -5,21 +5,17 @@
# https://docs.docker.com/develop/develop-images/multistage-build/ # https://docs.docker.com/develop/develop-images/multistage-build/
# https://whitfin.io/speeding-up-rust-docker-builds/ # https://whitfin.io/speeding-up-rust-docker-builds/
####################### VAULT BUILD IMAGE ####################### ####################### VAULT BUILD IMAGE #######################
FROM alpine:3.11 as vault
ENV VAULT_VERSION "v2.12.0c" # 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:
ENV URL "https://github.com/dani-garcia/bw_web_builds/releases/download/$VAULT_VERSION/bw_web_$VAULT_VERSION.tar.gz" # - 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:
RUN apk add --no-cache --upgrade curl tar # docker pull bitwardenrs/web-vault:v2.12.0e
# docker image inspect --format "{{.RepoDigests}}" bitwardenrs/web-vault:v2.12.0e
RUN mkdir /web-vault #
WORKDIR /web-vault # - To do the opposite, and get the tag from the hash, you can do:
# docker image inspect --format "{{.RepoTags}}" bitwardenrs/web-vault@sha256:feb3f46d15738191b9043be4cdb1be2c0078ed411e7b7be73a2f4fcbca01e13c
SHELL ["/bin/ash", "-o", "nounset", "-o", "pipefail", "-o", "errexit", "-c"] FROM bitwardenrs/web-vault@sha256:feb3f46d15738191b9043be4cdb1be2c0078ed411e7b7be73a2f4fcbca01e13c as vault
RUN curl -L $URL | tar xz
RUN ls
########################## BUILD IMAGE ########################## ########################## BUILD IMAGE ##########################
# Musl build image for statically compiled binary # Musl build image for statically compiled binary

View file

@ -5,22 +5,17 @@
# https://docs.docker.com/develop/develop-images/multistage-build/ # https://docs.docker.com/develop/develop-images/multistage-build/
# https://whitfin.io/speeding-up-rust-docker-builds/ # https://whitfin.io/speeding-up-rust-docker-builds/
####################### VAULT BUILD IMAGE ####################### ####################### VAULT BUILD IMAGE #######################
FROM rust:1.40 as vault
ENV VAULT_VERSION "v2.12.0c" # 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:
ENV URL "https://github.com/dani-garcia/bw_web_builds/releases/download/$VAULT_VERSION/bw_web_$VAULT_VERSION.tar.gz" # - 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:
# Build time options to avoid dpkg warnings and help with reproducible builds. # docker pull bitwardenrs/web-vault:v2.12.0e
ENV DEBIAN_FRONTEND=noninteractive LANG=C.UTF-8 TZ=UTC TERM=xterm-256color # docker image inspect --format "{{.RepoDigests}}" bitwardenrs/web-vault:v2.12.0e
#
RUN mkdir /web-vault # - To do the opposite, and get the tag from the hash, you can do:
WORKDIR /web-vault # docker image inspect --format "{{.RepoTags}}" bitwardenrs/web-vault@sha256:feb3f46d15738191b9043be4cdb1be2c0078ed411e7b7be73a2f4fcbca01e13c
FROM bitwardenrs/web-vault@sha256:feb3f46d15738191b9043be4cdb1be2c0078ed411e7b7be73a2f4fcbca01e13c as vault
SHELL ["/bin/bash", "-o", "nounset", "-o", "pipefail", "-o", "errexit", "-c"]
RUN curl -L $URL | tar xz
RUN ls
########################## BUILD IMAGE ########################## ########################## BUILD IMAGE ##########################
# We need to use the Rust build image, because # We need to use the Rust build image, because

View file

@ -5,21 +5,17 @@
# https://docs.docker.com/develop/develop-images/multistage-build/ # https://docs.docker.com/develop/develop-images/multistage-build/
# https://whitfin.io/speeding-up-rust-docker-builds/ # https://whitfin.io/speeding-up-rust-docker-builds/
####################### VAULT BUILD IMAGE ####################### ####################### VAULT BUILD IMAGE #######################
FROM alpine:3.11 as vault
ENV VAULT_VERSION "v2.12.0c" # 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:
ENV URL "https://github.com/dani-garcia/bw_web_builds/releases/download/$VAULT_VERSION/bw_web_$VAULT_VERSION.tar.gz" # - 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:
RUN apk add --no-cache --upgrade curl tar # docker pull bitwardenrs/web-vault:v2.12.0e
# docker image inspect --format "{{.RepoDigests}}" bitwardenrs/web-vault:v2.12.0e
RUN mkdir /web-vault #
WORKDIR /web-vault # - To do the opposite, and get the tag from the hash, you can do:
# docker image inspect --format "{{.RepoTags}}" bitwardenrs/web-vault@sha256:feb3f46d15738191b9043be4cdb1be2c0078ed411e7b7be73a2f4fcbca01e13c
SHELL ["/bin/ash", "-o", "nounset", "-o", "pipefail", "-o", "errexit", "-c"] FROM bitwardenrs/web-vault@sha256:feb3f46d15738191b9043be4cdb1be2c0078ed411e7b7be73a2f4fcbca01e13c as vault
RUN curl -L $URL | tar xz
RUN ls
########################## BUILD IMAGE ########################## ########################## BUILD IMAGE ##########################
# Musl build image for statically compiled binary # Musl build image for statically compiled binary

View file

@ -5,22 +5,17 @@
# https://docs.docker.com/develop/develop-images/multistage-build/ # https://docs.docker.com/develop/develop-images/multistage-build/
# https://whitfin.io/speeding-up-rust-docker-builds/ # https://whitfin.io/speeding-up-rust-docker-builds/
####################### VAULT BUILD IMAGE ####################### ####################### VAULT BUILD IMAGE #######################
FROM rust:1.40 as vault
ENV VAULT_VERSION "v2.12.0c" # 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:
ENV URL "https://github.com/dani-garcia/bw_web_builds/releases/download/$VAULT_VERSION/bw_web_$VAULT_VERSION.tar.gz" # - 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:
# Build time options to avoid dpkg warnings and help with reproducible builds. # docker pull bitwardenrs/web-vault:v2.12.0e
ENV DEBIAN_FRONTEND=noninteractive LANG=C.UTF-8 TZ=UTC TERM=xterm-256color # docker image inspect --format "{{.RepoDigests}}" bitwardenrs/web-vault:v2.12.0e
#
RUN mkdir /web-vault # - To do the opposite, and get the tag from the hash, you can do:
WORKDIR /web-vault # docker image inspect --format "{{.RepoTags}}" bitwardenrs/web-vault@sha256:feb3f46d15738191b9043be4cdb1be2c0078ed411e7b7be73a2f4fcbca01e13c
FROM bitwardenrs/web-vault@sha256:feb3f46d15738191b9043be4cdb1be2c0078ed411e7b7be73a2f4fcbca01e13c as vault
SHELL ["/bin/bash", "-o", "nounset", "-o", "pipefail", "-o", "errexit", "-c"]
RUN curl -L $URL | tar xz
RUN ls
########################## BUILD IMAGE ########################## ########################## BUILD IMAGE ##########################
# We need to use the Rust build image, because # We need to use the Rust build image, because

View file

@ -5,22 +5,17 @@
# https://docs.docker.com/develop/develop-images/multistage-build/ # https://docs.docker.com/develop/develop-images/multistage-build/
# https://whitfin.io/speeding-up-rust-docker-builds/ # https://whitfin.io/speeding-up-rust-docker-builds/
####################### VAULT BUILD IMAGE ####################### ####################### VAULT BUILD IMAGE #######################
FROM rust:1.40 as vault
ENV VAULT_VERSION "v2.12.0c" # 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:
ENV URL "https://github.com/dani-garcia/bw_web_builds/releases/download/$VAULT_VERSION/bw_web_$VAULT_VERSION.tar.gz" # - 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:
# Build time options to avoid dpkg warnings and help with reproducible builds. # docker pull bitwardenrs/web-vault:v2.12.0e
ENV DEBIAN_FRONTEND=noninteractive LANG=C.UTF-8 TZ=UTC TERM=xterm-256color # docker image inspect --format "{{.RepoDigests}}" bitwardenrs/web-vault:v2.12.0e
#
RUN mkdir /web-vault # - To do the opposite, and get the tag from the hash, you can do:
WORKDIR /web-vault # docker image inspect --format "{{.RepoTags}}" bitwardenrs/web-vault@sha256:feb3f46d15738191b9043be4cdb1be2c0078ed411e7b7be73a2f4fcbca01e13c
FROM bitwardenrs/web-vault@sha256:feb3f46d15738191b9043be4cdb1be2c0078ed411e7b7be73a2f4fcbca01e13c as vault
SHELL ["/bin/bash", "-o", "nounset", "-o", "pipefail", "-o", "errexit", "-c"]
RUN curl -L $URL | tar xz
RUN ls
########################## BUILD IMAGE ########################## ########################## BUILD IMAGE ##########################
# We need to use the Rust build image, because # We need to use the Rust build image, because

View file

@ -5,22 +5,17 @@
# https://docs.docker.com/develop/develop-images/multistage-build/ # https://docs.docker.com/develop/develop-images/multistage-build/
# https://whitfin.io/speeding-up-rust-docker-builds/ # https://whitfin.io/speeding-up-rust-docker-builds/
####################### VAULT BUILD IMAGE ####################### ####################### VAULT BUILD IMAGE #######################
FROM rust:1.40 as vault
ENV VAULT_VERSION "v2.12.0c" # 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:
ENV URL "https://github.com/dani-garcia/bw_web_builds/releases/download/$VAULT_VERSION/bw_web_$VAULT_VERSION.tar.gz" # - 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:
# Build time options to avoid dpkg warnings and help with reproducible builds. # docker pull bitwardenrs/web-vault:v2.12.0e
ENV DEBIAN_FRONTEND=noninteractive LANG=C.UTF-8 TZ=UTC TERM=xterm-256color # docker image inspect --format "{{.RepoDigests}}" bitwardenrs/web-vault:v2.12.0e
#
RUN mkdir /web-vault # - To do the opposite, and get the tag from the hash, you can do:
WORKDIR /web-vault # docker image inspect --format "{{.RepoTags}}" bitwardenrs/web-vault@sha256:feb3f46d15738191b9043be4cdb1be2c0078ed411e7b7be73a2f4fcbca01e13c
FROM bitwardenrs/web-vault@sha256:feb3f46d15738191b9043be4cdb1be2c0078ed411e7b7be73a2f4fcbca01e13c as vault
SHELL ["/bin/bash", "-o", "nounset", "-o", "pipefail", "-o", "errexit", "-c"]
RUN curl -L $URL | tar xz
RUN ls
########################## BUILD IMAGE ########################## ########################## BUILD IMAGE ##########################
# We need to use the Rust build image, because # We need to use the Rust build image, because

View file

@ -5,22 +5,17 @@
# https://docs.docker.com/develop/develop-images/multistage-build/ # https://docs.docker.com/develop/develop-images/multistage-build/
# https://whitfin.io/speeding-up-rust-docker-builds/ # https://whitfin.io/speeding-up-rust-docker-builds/
####################### VAULT BUILD IMAGE ####################### ####################### VAULT BUILD IMAGE #######################
FROM rust:1.40 as vault
ENV VAULT_VERSION "v2.12.0c" # 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:
ENV URL "https://github.com/dani-garcia/bw_web_builds/releases/download/$VAULT_VERSION/bw_web_$VAULT_VERSION.tar.gz" # - 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:
# Build time options to avoid dpkg warnings and help with reproducible builds. # docker pull bitwardenrs/web-vault:v2.12.0e
ENV DEBIAN_FRONTEND=noninteractive LANG=C.UTF-8 TZ=UTC TERM=xterm-256color # docker image inspect --format "{{.RepoDigests}}" bitwardenrs/web-vault:v2.12.0e
#
RUN mkdir /web-vault # - To do the opposite, and get the tag from the hash, you can do:
WORKDIR /web-vault # docker image inspect --format "{{.RepoTags}}" bitwardenrs/web-vault@sha256:feb3f46d15738191b9043be4cdb1be2c0078ed411e7b7be73a2f4fcbca01e13c
FROM bitwardenrs/web-vault@sha256:feb3f46d15738191b9043be4cdb1be2c0078ed411e7b7be73a2f4fcbca01e13c as vault
SHELL ["/bin/bash", "-o", "nounset", "-o", "pipefail", "-o", "errexit", "-c"]
RUN curl -L $URL | tar xz
RUN ls
########################## BUILD IMAGE ########################## ########################## BUILD IMAGE ##########################
# We need to use the Rust build image, because # We need to use the Rust build image, because

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-2020-01-30 nightly-2020-03-09

View file

@ -1,3 +1,4 @@
use once_cell::sync::Lazy;
use serde_json::Value; use serde_json::Value;
use std::process::Command; use std::process::Command;
@ -34,12 +35,12 @@ pub fn routes() -> Vec<Route> {
post_config, post_config,
delete_config, delete_config,
backup_db, backup_db,
test_smtp,
] ]
} }
lazy_static! { static CAN_BACKUP: Lazy<bool> =
static ref CAN_BACKUP: bool = cfg!(feature = "sqlite") && Command::new("sqlite3").arg("-version").status().is_ok(); Lazy::new(|| cfg!(feature = "sqlite") && Command::new("sqlite3").arg("-version").status().is_ok());
}
#[get("/")] #[get("/")]
fn admin_disabled() -> &'static str { fn admin_disabled() -> &'static str {
@ -52,11 +53,15 @@ const ADMIN_PATH: &str = "/admin";
const BASE_TEMPLATE: &str = "admin/base"; const BASE_TEMPLATE: &str = "admin/base";
const VERSION: Option<&str> = option_env!("GIT_VERSION"); const VERSION: Option<&str> = option_env!("GIT_VERSION");
fn admin_path() -> String {
format!("{}{}", CONFIG.domain_path(), ADMIN_PATH)
}
#[get("/", rank = 2)] #[get("/", rank = 2)]
fn admin_login(flash: Option<FlashMessage>) -> ApiResult<Html<String>> { fn admin_login(flash: Option<FlashMessage>) -> ApiResult<Html<String>> {
// If there is an error, show it // If there is an error, show it
let msg = flash.map(|msg| format!("{}: {}", msg.name(), msg.msg())); let msg = flash.map(|msg| format!("{}: {}", msg.name(), msg.msg()));
let json = json!({"page_content": "admin/login", "version": VERSION, "error": msg}); let json = json!({"page_content": "admin/login", "version": VERSION, "error": msg, "urlpath": CONFIG.domain_path()});
// Return the page // Return the page
let text = CONFIG.render_template(BASE_TEMPLATE, &json)?; let text = CONFIG.render_template(BASE_TEMPLATE, &json)?;
@ -76,7 +81,7 @@ fn post_admin_login(data: Form<LoginForm>, mut cookies: Cookies, ip: ClientIp) -
if !_validate_token(&data.token) { if !_validate_token(&data.token) {
error!("Invalid admin token. IP: {}", ip.ip); error!("Invalid admin token. IP: {}", ip.ip);
Err(Flash::error( Err(Flash::error(
Redirect::to(ADMIN_PATH), Redirect::to(admin_path()),
"Invalid admin token, please try again.", "Invalid admin token, please try again.",
)) ))
} else { } else {
@ -85,14 +90,14 @@ fn post_admin_login(data: Form<LoginForm>, mut cookies: Cookies, ip: ClientIp) -
let jwt = encode_jwt(&claims); let jwt = encode_jwt(&claims);
let cookie = Cookie::build(COOKIE_NAME, jwt) let cookie = Cookie::build(COOKIE_NAME, jwt)
.path(ADMIN_PATH) .path(admin_path())
.max_age(chrono::Duration::minutes(20)) .max_age(chrono::Duration::minutes(20))
.same_site(SameSite::Strict) .same_site(SameSite::Strict)
.http_only(true) .http_only(true)
.finish(); .finish();
cookies.add(cookie); cookies.add(cookie);
Ok(Redirect::to(ADMIN_PATH)) Ok(Redirect::to(admin_path()))
} }
} }
@ -111,6 +116,7 @@ struct AdminTemplateData {
config: Value, config: Value,
can_backup: bool, can_backup: bool,
logged_in: bool, logged_in: bool,
urlpath: String,
} }
impl AdminTemplateData { impl AdminTemplateData {
@ -122,6 +128,7 @@ impl AdminTemplateData {
config: CONFIG.prepare_json(), config: CONFIG.prepare_json(),
can_backup: *CAN_BACKUP, can_backup: *CAN_BACKUP,
logged_in: true, logged_in: true,
urlpath: CONFIG.domain_path(),
} }
} }
@ -164,10 +171,22 @@ fn invite_user(data: Json<InviteData>, _token: AdminToken, conn: DbConn) -> Empt
} }
} }
#[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")] #[get("/logout")]
fn logout(mut cookies: Cookies) -> Result<Redirect, ()> { fn logout(mut cookies: Cookies) -> Result<Redirect, ()> {
cookies.remove(Cookie::named(COOKIE_NAME)); cookies.remove(Cookie::named(COOKIE_NAME));
Ok(Redirect::to(ADMIN_PATH)) Ok(Redirect::to(admin_path()))
} }
#[get("/users")] #[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 = Collection::find_by_user_uuid(&headers.user.uuid, &conn);
let collections_json: Vec<Value> = collections.iter().map(Collection::to_json).collect(); 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 = Cipher::find_by_user(&headers.user.uuid, &conn);
let ciphers_json: Vec<Value> = ciphers let ciphers_json: Vec<Value> = ciphers
.iter() .iter()
@ -95,6 +98,7 @@ fn sync(data: Form<SyncData>, headers: Headers, conn: DbConn) -> JsonResult {
"Profile": user_json, "Profile": user_json,
"Folders": folders_json, "Folders": folders_json,
"Collections": collections_json, "Collections": collections_json,
"Policies": policies_json,
"Ciphers": ciphers_json, "Ciphers": ciphers_json,
"Domains": domains_json, "Domains": domains_json,
"Object": "sync" "Object": "sync"
@ -599,10 +603,14 @@ fn share_cipher_by_uuid(
None => err!("Cipher doesn't exist"), 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; 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 { for uuid in &data.CollectionIds {
match Collection::find_by_uuid_and_org(uuid, &organization_uuid, &conn) { match Collection::find_by_uuid_and_org(uuid, &organization_uuid, &conn) {
None => err!("Invalid collection ID provided"), None => err!("Invalid collection ID provided"),
@ -616,6 +624,9 @@ fn share_cipher_by_uuid(
} }
} }
} }
}
};
update_cipher_from_data( update_cipher_from_data(
&mut cipher, &mut cipher,
data.Cipher, data.Cipher,
@ -628,8 +639,6 @@ fn share_cipher_by_uuid(
Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, &conn))) Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, &conn)))
} }
}
}
#[post("/ciphers/<uuid>/attachment", format = "multipart/form-data", data = "<data>")] #[post("/ciphers/<uuid>/attachment", format = "multipart/form-data", data = "<data>")]
fn post_attachment( fn post_attachment(

View file

@ -146,10 +146,10 @@ fn hibp_breach(username: String) -> JsonResult {
username username
); );
use reqwest::{header::USER_AGENT, Client}; use reqwest::{header::USER_AGENT, blocking::Client};
if let Some(api_key) = crate::CONFIG.hibp_api_key() { if let Some(api_key) = crate::CONFIG.hibp_api_key() {
let hibp_client = Client::builder().use_sys_proxy().build()?; let hibp_client = Client::builder().build()?;
let res = hibp_client let res = hibp_client
.get(&url) .get(&url)
@ -172,7 +172,7 @@ fn hibp_breach(username: String) -> JsonResult {
"BreachDate": "2019-08-18T00:00:00Z", "BreachDate": "2019-08-18T00:00:00Z",
"AddedDate": "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), "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, "PwnCount": 0,
"DataClasses": [ "DataClasses": [
"Error - No API key set!" "Error - No API key set!"

View file

@ -2,6 +2,7 @@ use rocket::request::Form;
use rocket::Route; use rocket::Route;
use rocket_contrib::json::Json; use rocket_contrib::json::Json;
use serde_json::Value; use serde_json::Value;
use num_traits::FromPrimitive;
use crate::api::{ use crate::api::{
EmptyResult, JsonResult, JsonUpcase, JsonUpcaseVec, Notify, NumberOrString, PasswordData, UpdateType, EmptyResult, JsonResult, JsonUpcase, JsonUpcaseVec, Notify, NumberOrString, PasswordData, UpdateType,
@ -45,6 +46,9 @@ pub fn routes() -> Vec<Route> {
delete_user, delete_user,
post_delete_user, post_delete_user,
post_org_import, post_org_import,
list_policies,
get_policy,
put_policy,
] ]
} }
@ -830,22 +834,13 @@ struct RelationsData {
fn post_org_import( fn post_org_import(
query: Form<OrgIdData>, query: Form<OrgIdData>,
data: JsonUpcase<ImportData>, data: JsonUpcase<ImportData>,
headers: Headers, headers: AdminHeaders,
conn: DbConn, conn: DbConn,
nt: Notify, nt: Notify,
) -> EmptyResult { ) -> EmptyResult {
let data: ImportData = data.into_inner().data; let data: ImportData = data.into_inner().data;
let org_id = query.into_inner().organization_id; 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 // Read and create the collections
let collections: Vec<_> = data let collections: Vec<_> = data
.Collections .Collections
@ -866,6 +861,8 @@ fn post_org_import(
relations.push((relation.Key, relation.Value)); relations.push((relation.Key, relation.Value));
} }
let headers: Headers = headers.into();
// Read and create the ciphers // Read and create the ciphers
let ciphers: Vec<_> = data let ciphers: Vec<_> = data
.Ciphers .Ciphers
@ -901,3 +898,59 @@ fn post_org_import(
let mut user = headers.user; let mut user = headers.user;
user.update_revision(&conn) 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/<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

@ -187,7 +187,7 @@ fn activate_duo_put(data: JsonUpcase<EnableDuoData>, headers: Headers, conn: DbC
fn duo_api_request(method: &str, path: &str, params: &str, data: &DuoData) -> EmptyResult { fn duo_api_request(method: &str, path: &str, params: &str, data: &DuoData) -> EmptyResult {
const AGENT: &str = "bitwarden_rs:Duo/1.0 (Rust)"; const AGENT: &str = "bitwarden_rs:Duo/1.0 (Rust)";
use reqwest::{header::*, Client, Method}; use reqwest::{header::*, Method, blocking::Client};
use std::str::FromStr; use std::str::FromStr;
let url = format!("https://{}{}", &data.host, path); let url = format!("https://{}{}", &data.host, path);

View file

@ -1,3 +1,4 @@
use once_cell::sync::Lazy;
use rocket::Route; use rocket::Route;
use rocket_contrib::json::Json; use rocket_contrib::json::Json;
use serde_json; use serde_json;
@ -18,10 +19,8 @@ use crate::CONFIG;
const U2F_VERSION: &str = "U2F_V2"; const U2F_VERSION: &str = "U2F_V2";
lazy_static! { static APP_ID: Lazy<String> = Lazy::new(|| format!("{}/app-id.json", &CONFIG.domain()));
static ref APP_ID: String = format!("{}/app-id.json", &CONFIG.domain()); static U2F: Lazy<U2f> = Lazy::new(|| U2f::new(APP_ID.clone()));
static ref U2F: U2f = U2f::new(APP_ID.clone());
}
pub fn routes() -> Vec<Route> { pub fn routes() -> Vec<Route> {
routes![ routes![

View file

@ -1,3 +1,4 @@
use once_cell::sync::Lazy;
use std::fs::{create_dir_all, remove_file, symlink_metadata, File}; use std::fs::{create_dir_all, remove_file, symlink_metadata, File};
use std::io::prelude::*; use std::io::prelude::*;
use std::net::ToSocketAddrs; use std::net::ToSocketAddrs;
@ -7,7 +8,7 @@ use rocket::http::ContentType;
use rocket::response::Content; use rocket::response::Content;
use rocket::Route; use rocket::Route;
use reqwest::{header::HeaderMap, Client, Response, Url}; use reqwest::{Url, header::HeaderMap, blocking::Client, blocking::Response};
use rocket::http::Cookie; use rocket::http::Cookie;
@ -26,16 +27,14 @@ const FALLBACK_ICON: &[u8; 344] = include_bytes!("../static/fallback-icon.png");
const ALLOWED_CHARS: &str = "_-."; const ALLOWED_CHARS: &str = "_-.";
lazy_static! { static CLIENT: Lazy<Client> = Lazy::new(|| {
// Reuse the client between requests // Reuse the client between requests
static ref CLIENT: Client = Client::builder() Client::builder()
.use_sys_proxy()
.gzip(true)
.timeout(Duration::from_secs(CONFIG.icon_download_timeout())) .timeout(Duration::from_secs(CONFIG.icon_download_timeout()))
.default_headers(_header_map()) .default_headers(_header_map())
.build() .build()
.unwrap(); .unwrap()
} });
fn is_valid_domain(domain: &str) -> bool { fn is_valid_domain(domain: &str) -> bool {
// Don't allow empty or too big domains or path traversal // Don't allow empty or too big domains or path traversal

View file

@ -37,7 +37,17 @@ fn app_id() -> Cached<Content<Json<Value>>> {
{ {
"version": { "major": 1, "minor": 0 }, "version": { "major": 1, "minor": 0 },
"ids": [ "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", "ios:bundle-id:com.8bit.bitwarden",
"android:apk-key-hash:dUGFzUzf3lmHSLBDBIv+WaFyZMI" ] "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"))), "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"))), "md5.js" => Ok(Content(ContentType::JavaScript, include_bytes!("../static/scripts/md5.js"))),
"identicon.js" => Ok(Content(ContentType::JavaScript, include_bytes!("../static/scripts/identicon.js"))), "identicon.js" => Ok(Content(ContentType::JavaScript, include_bytes!("../static/scripts/identicon.js"))),
_ => err!("Image not found"), _ => err!(format!("Static file not found: {}", filename)),
} }
} }

View file

@ -3,6 +3,8 @@
// //
use crate::util::read_file; use crate::util::read_file;
use chrono::{Duration, Utc}; use chrono::{Duration, Utc};
use once_cell::sync::Lazy;
use num_traits::FromPrimitive;
use jsonwebtoken::{self, Algorithm, Header}; use jsonwebtoken::{self, Algorithm, Header};
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
@ -13,23 +15,21 @@ use crate::CONFIG;
const JWT_ALGORITHM: Algorithm = Algorithm::RS256; const JWT_ALGORITHM: Algorithm = Algorithm::RS256;
lazy_static! { pub static DEFAULT_VALIDITY: Lazy<Duration> = Lazy::new(|| Duration::hours(2));
pub static ref DEFAULT_VALIDITY: Duration = Duration::hours(2); static JWT_HEADER: Lazy<Header> = Lazy::new(|| Header::new(JWT_ALGORITHM));
static ref JWT_HEADER: Header = Header::new(JWT_ALGORITHM); pub static JWT_LOGIN_ISSUER: Lazy<String> = Lazy::new(|| format!("{}|login", CONFIG.domain_origin()));
pub static ref JWT_LOGIN_ISSUER: String = format!("{}|login", CONFIG.domain()); static JWT_INVITE_ISSUER: Lazy<String> = Lazy::new(|| format!("{}|invite", CONFIG.domain_origin()));
pub static ref JWT_INVITE_ISSUER: String = format!("{}|invite", CONFIG.domain()); static JWT_DELETE_ISSUER: Lazy<String> = Lazy::new(|| format!("{}|delete", CONFIG.domain_origin()));
pub static ref JWT_DELETE_ISSUER: String = format!("{}|delete", CONFIG.domain()); static JWT_VERIFYEMAIL_ISSUER: Lazy<String> = Lazy::new(|| format!("{}|verifyemail", CONFIG.domain_origin()));
pub static ref JWT_VERIFYEMAIL_ISSUER: String = format!("{}|verifyemail", CONFIG.domain()); static JWT_ADMIN_ISSUER: Lazy<String> = Lazy::new(|| format!("{}|admin", CONFIG.domain_origin()));
pub static ref JWT_ADMIN_ISSUER: String = format!("{}|admin", CONFIG.domain()); static PRIVATE_RSA_KEY: Lazy<Vec<u8>> = Lazy::new(|| match read_file(&CONFIG.private_rsa_key()) {
static ref PRIVATE_RSA_KEY: Vec<u8> = match read_file(&CONFIG.private_rsa_key()) {
Ok(key) => key, Ok(key) => key,
Err(e) => panic!("Error loading private RSA Key.\n Error: {}", e), 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, Ok(key) => key,
Err(e) => panic!("Error loading public RSA Key.\n Error: {}", e), Err(e) => panic!("Error loading public RSA Key.\n Error: {}", e),
}; });
}
pub fn encode_jwt<T: Serialize>(claims: &T) -> String { pub fn encode_jwt<T: Serialize>(claims: &T) -> String {
match jsonwebtoken::encode(&JWT_HEADER, claims, &PRIVATE_RSA_KEY) { match jsonwebtoken::encode(&JWT_HEADER, claims, &PRIVATE_RSA_KEY) {
@ -386,6 +386,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 struct OwnerHeaders {
pub host: String, pub host: String,
pub device: Device, pub device: Device,

View file

@ -1,19 +1,23 @@
use once_cell::sync::Lazy;
use std::process::exit; use std::process::exit;
use std::sync::RwLock; use std::sync::RwLock;
use reqwest::Url;
use crate::error::Error; use crate::error::Error;
use crate::util::{get_env, get_env_bool}; use crate::util::{get_env, get_env_bool};
lazy_static! { static CONFIG_FILE: Lazy<String> = Lazy::new(|| {
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 = {
let data_folder = get_env("DATA_FOLDER").unwrap_or_else(|| String::from("data")); 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)) 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; pub type Pass = String;
@ -240,6 +244,10 @@ make_config! {
domain: String, true, def, "http://localhost".to_string(); 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 |> Indicates if the domain is set by the admin. Otherwise the default will be used.
domain_set: bool, false, def, false; 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 /// Enable web vault
web_vault_enabled: bool, false, def, true; web_vault_enabled: bool, false, def, true;
@ -415,6 +423,11 @@ fn validate_config(cfg: &ConfigItems) -> Result<(), Error> {
err!("`DATABASE_URL` should start with postgresql: when using the PostgreSQL server") 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 let Some(ref token) = cfg.admin_token {
if token.trim().is_empty() && !cfg.disable_admin_token { if token.trim().is_empty() && !cfg.disable_admin_token {
err!("`ADMIN_TOKEN` is enabled but has an empty value. To enable the admin page without token, use `DISABLE_ADMIN_TOKEN`") err!("`ADMIN_TOKEN` is enabled but has an empty value. To enable the admin page without token, use `DISABLE_ADMIN_TOKEN`")
@ -457,6 +470,29 @@ fn validate_config(cfg: &ConfigItems) -> Result<(), Error> {
Ok(()) 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 { impl Config {
pub fn load() -> Result<Self, Error> { pub fn load() -> Result<Self, Error> {
// Loading from env and file // Loading from env and file
@ -634,6 +670,7 @@ where
reg!("email/verify_email", ".html"); reg!("email/verify_email", ".html");
reg!("email/welcome", ".html"); reg!("email/welcome", ".html");
reg!("email/welcome_must_verify", ".html"); reg!("email/welcome_must_verify", ".html");
reg!("email/smtp_test", ".html");
reg!("admin/base"); reg!("admin/base");
reg!("admin/login"); reg!("admin/login");

View file

@ -7,6 +7,7 @@ mod user;
mod collection; mod collection;
mod organization; mod organization;
mod two_factor; mod two_factor;
mod org_policy;
pub use self::attachment::Attachment; pub use self::attachment::Attachment;
pub use self::cipher::Cipher; pub use self::cipher::Cipher;
@ -17,3 +18,4 @@ pub use self::organization::Organization;
pub use self::organization::{UserOrgStatus, UserOrgType, UserOrganization}; pub use self::organization::{UserOrgStatus, UserOrgType, UserOrganization};
pub use self::two_factor::{TwoFactor, TwoFactorType}; pub use self::two_factor::{TwoFactor, TwoFactorType};
pub use self::user::{Invitation, User}; 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 serde_json::Value;
use std::cmp::Ordering; use std::cmp::Ordering;
use num_traits::FromPrimitive;
use super::{CollectionUser, User}; use super::{CollectionUser, User, OrgPolicy};
#[derive(Debug, Identifiable, Queryable, Insertable, AsChangeset)] #[derive(Debug, Identifiable, Queryable, Insertable, AsChangeset)]
#[table_name = "organizations"] #[table_name = "organizations"]
@ -33,6 +34,7 @@ pub enum UserOrgStatus {
} }
#[derive(Copy, Clone, PartialEq, Eq)] #[derive(Copy, Clone, PartialEq, Eq)]
#[derive(FromPrimitive)]
pub enum UserOrgType { pub enum UserOrgType {
Owner = 0, Owner = 0,
Admin = 1, Admin = 1,
@ -135,16 +137,6 @@ impl UserOrgType {
_ => None, _ => 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 /// Local methods
@ -170,6 +162,7 @@ impl Organization {
"UseEvents": false, "UseEvents": false,
"UseGroups": false, "UseGroups": false,
"UseTotp": true, "UseTotp": true,
"UsePolicies": true,
"BusinessName": null, "BusinessName": null,
"BusinessAddress1": null, "BusinessAddress1": null,
@ -250,6 +243,7 @@ impl Organization {
Cipher::delete_all_by_organization(&self.uuid, &conn)?; Cipher::delete_all_by_organization(&self.uuid, &conn)?;
Collection::delete_all_by_organization(&self.uuid, &conn)?; Collection::delete_all_by_organization(&self.uuid, &conn)?;
UserOrganization::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))) diesel::delete(organizations::table.filter(organizations::uuid.eq(self.uuid)))
.execute(&**conn) .execute(&**conn)
@ -280,6 +274,7 @@ impl UserOrganization {
"UseEvents": false, "UseEvents": false,
"UseGroups": false, "UseGroups": false,
"UseTotp": true, "UseTotp": true,
"UsePolicies": true,
"MaxStorageGb": 10, // The value doesn't matter, we don't check server-side "MaxStorageGb": 10, // The value doesn't matter, we don't check server-side

View file

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

View file

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

View file

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

View file

@ -18,21 +18,21 @@ use chrono::NaiveDateTime;
fn mailer() -> SmtpTransport { fn mailer() -> SmtpTransport {
let host = CONFIG.smtp_host().unwrap(); let host = CONFIG.smtp_host().unwrap();
let client_security = if CONFIG.smtp_ssl() {
let tls = TlsConnector::builder() let tls = TlsConnector::builder()
.min_protocol_version(Some(Protocol::Tlsv11)) .min_protocol_version(Some(Protocol::Tlsv11))
.build() .build()
.unwrap(); .unwrap();
let params = ClientTlsParameters::new(host.clone(), tls); let tls_params = ClientTlsParameters::new(host.clone(), tls);
let client_security = if CONFIG.smtp_ssl() {
if CONFIG.smtp_explicit_tls() { if CONFIG.smtp_explicit_tls() {
ClientSecurity::Wrapper(params) ClientSecurity::Wrapper(tls_params)
} else { } else {
ClientSecurity::Required(params) ClientSecurity::Required(tls_params)
} }
} else { } else {
ClientSecurity::None ClientSecurity::Opportunistic(tls_params)
}; };
use std::time::Duration; use std::time::Duration;
@ -44,10 +44,11 @@ fn mailer() -> SmtpTransport {
_ => smtp_client, _ => smtp_client,
}; };
let smtp_client = match &CONFIG.smtp_auth_mechanism() { let smtp_client = match CONFIG.smtp_auth_mechanism() {
Some(auth_mechanism_json) => { Some(mechanism) => {
let auth_mechanism = serde_json::from_str::<SmtpAuthMechanism>(&auth_mechanism_json); let correct_mechanism = format!("\"{}\"", crate::util::upcase_first(&mechanism.trim_matches('"')));
match auth_mechanism {
match serde_json::from_str::<SmtpAuthMechanism>(&correct_mechanism) {
Ok(auth_mechanism) => smtp_client.authentication_mechanism(auth_mechanism), Ok(auth_mechanism) => smtp_client.authentication_mechanism(auth_mechanism),
_ => panic!("Failure to parse mechanism. Is it proper Json? Eg. `\"Plain\"` not `Plain`"), _ => panic!("Failure to parse mechanism. Is it proper Json? Eg. `\"Plain\"` not `Plain`"),
} }
@ -258,6 +259,17 @@ pub fn send_change_email(address: &str, token: &str) -> EmptyResult {
send_email(&address, &subject, &body_html, &body_text) send_email(&address, &subject, &body_html, &body_text)
} }
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 { fn send_email(address: &str, subject: &str, body_html: &str, body_text: &str) -> EmptyResult {
let address_split: Vec<&str> = address.rsplitn(2, '@').collect(); let address_split: Vec<&str> = address.rsplitn(2, '@').collect();
if address_split.len() != 2 { if address_split.len() != 2 {

View file

@ -16,8 +16,6 @@ extern crate diesel;
#[macro_use] #[macro_use]
extern crate diesel_migrations; extern crate diesel_migrations;
#[macro_use] #[macro_use]
extern crate lazy_static;
#[macro_use]
extern crate derive_more; extern crate derive_more;
#[macro_use] #[macro_use]
extern crate num_derive; extern crate num_derive;
@ -43,7 +41,18 @@ mod util;
pub use config::CONFIG; pub use config::CONFIG;
pub use error::{Error, MapResult}; pub use error::{Error, MapResult};
use structopt::StructOpt;
#[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() { fn main() {
parse_args();
launch_info(); launch_info();
use log::LevelFilter as LF; use log::LevelFilter as LF;
@ -65,6 +74,18 @@ fn main() {
launch_rocket(extra_debug); launch_rocket(extra_debug);
} }
fn parse_args() {
let opt = Opt::from_args();
if opt.version {
if let Some(version) = option_env!("GIT_VERSION") {
println!("bitwarden_rs {}", version);
} else {
println!("bitwarden_rs (Version info from Git not present)");
}
exit(0);
}
}
fn launch_info() { fn launch_info() {
println!("/--------------------------------------------------------------------\\"); println!("/--------------------------------------------------------------------\\");
println!("| Starting Bitwarden_RS |"); println!("| Starting Bitwarden_RS |");
@ -183,7 +204,9 @@ fn check_rsa_keys() {
info!("JWT keys don't exist, checking if OpenSSL is available..."); info!("JWT keys don't exist, checking if OpenSSL is available...");
Command::new("openssl").arg("version").status().unwrap_or_else(|_| { 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); exit(1);
}); });
@ -261,18 +284,20 @@ mod migrations {
} }
fn launch_rocket(extra_debug: bool) { 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(); 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 // crate::utils::LOGGED_ROUTES to make sure they appear in the log
let rocket = rocket let rocket = rocket
.mount("/", api::web_routes()) .mount(&[basepath, "/"].concat(), api::web_routes())
.mount("/api", api::core_routes()) .mount(&[basepath, "/api"].concat(), api::core_routes())
.mount("/admin", api::admin_routes()) .mount(&[basepath, "/admin"].concat(), api::admin_routes())
.mount("/identity", api::identity_routes()) .mount(&[basepath, "/identity"].concat(), api::identity_routes())
.mount("/icons", api::icons_routes()) .mount(&[basepath, "/icons"].concat(), api::icons_routes())
.mount("/notifications", api::notifications_routes()) .mount(&[basepath, "/notifications"].concat(), api::notifications_routes())
.manage(db::init_pool()) .manage(db::init_pool())
.manage(api::start_notification_server()) .manage(api::start_notification_server())
.attach(util::AppHeaders()) .attach(util::AppHeaders())

View file

@ -775,5 +775,71 @@
"customercontrolpanel.de" "customercontrolpanel.de"
], ],
"Excluded": false "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"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Bitwarden_rs Admin Panel</title> <title>Bitwarden_rs Admin Panel</title>
<link rel="stylesheet" href="/bwrs_static/bootstrap.css" /> <link rel="stylesheet" href="{{urlpath}}/bwrs_static/bootstrap.css" />
<script src="/bwrs_static/bootstrap-native-v4.js"></script> <script src="{{urlpath}}/bwrs_static/bootstrap-native-v4.js"></script>
<script src="/bwrs_static/md5.js"></script> <script src="{{urlpath}}/bwrs_static/md5.js"></script>
<script src="/bwrs_static/identicon.js"></script> <script src="{{urlpath}}/bwrs_static/identicon.js"></script>
<style> <style>
body { body {
padding-top: 70px; padding-top: 70px;
@ -38,10 +38,10 @@
<div class="navbar-collapse"> <div class="navbar-collapse">
<ul class="navbar-nav"> <ul class="navbar-nav">
<li class="nav-item active"> <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>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="/">Vault</a> <a class="nav-link" href="{{urlpath}}/">Vault</a>
</li> </li>
</ul> </ul>
</div> </div>
@ -55,7 +55,7 @@
{{#if logged_in}} {{#if logged_in}}
<li class="nav-item"> <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> </li>
{{/if}} {{/if}}
</ul> </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 them to avoid confusion. This does not apply to the read-only section, which can only be set through the
environment. environment.
</div> </div>
<form class="form accordion" id="config-form" onsubmit="saveConfig(); return false;"> <form class="form accordion" id="config-form" onsubmit="saveConfig(); return false;">
{{#each config}} {{#each config}}
{{#if groupdoc}} {{#if groupdoc}}
@ -110,6 +111,17 @@
</div> </div>
{{/if}} {{/if}}
{{/each}} {{/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>
</div> </div>
{{/if}} {{/if}}
@ -191,7 +203,10 @@
<script> <script>
function reload() { window.location.reload(); } 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) { function identicon(email) {
const data = new Identicon(md5(email), { size: 48, format: 'svg' }); const data = new Identicon(md5(email), { size: 48, format: 'svg' });
return "data:image/svg+xml;base64," + data.toString(); return "data:image/svg+xml;base64," + data.toString();
@ -206,26 +221,37 @@
} }
return false; return false;
} }
function _post(url, successMsg, errMsg, body) { function _post(url, successMsg, errMsg, body, reload_page = true) {
fetch(url, { fetch(url, {
method: 'POST', method: 'POST',
body: body, body: body,
mode: "same-origin", mode: "same-origin",
credentials: "same-origin", credentials: "same-origin",
headers: { "Content-Type": "application/json" } headers: { "Content-Type": "application/json" }
}).then(e => { }).then( resp => {
if (e.ok) { return msg(successMsg); } if (resp.ok) { msg(successMsg, reload_page); return Promise.reject({error: false}); }
e.json().then(json => { respStatus = resp.status;
const msg = json ? json.ErrorModel.Message : "Unknown error"; respStatusText = resp.statusText;
msg(errMsg + ": " + msg); 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) { function deleteUser(id, mail) {
var input_mail = prompt("To delete user '" + mail + "', please type the email below") var input_mail = prompt("To delete user '" + mail + "', please type the email below")
if (input_mail != null) { if (input_mail != null) {
if (input_mail == mail) { if (input_mail == mail) {
_post("/admin/users/" + id + "/delete", _post("{{urlpath}}/admin/users/" + id + "/delete",
"User deleted correctly", "User deleted correctly",
"Error deleting user"); "Error deleting user");
} else { } else {
@ -235,19 +261,19 @@
return false; return false;
} }
function remove2fa(id) { function remove2fa(id) {
_post("/admin/users/" + id + "/remove-2fa", _post("{{urlpath}}/admin/users/" + id + "/remove-2fa",
"2FA removed correctly", "2FA removed correctly",
"Error removing 2FA"); "Error removing 2FA");
return false; return false;
} }
function deauthUser(id) { function deauthUser(id) {
_post("/admin/users/" + id + "/deauth", _post("{{urlpath}}/admin/users/" + id + "/deauth",
"Sessions deauthorized correctly", "Sessions deauthorized correctly",
"Error deauthorizing sessions"); "Error deauthorizing sessions");
return false; return false;
} }
function updateRevisions() { function updateRevisions() {
_post("/admin/users/update_revision", _post("{{urlpath}}/admin/users/update_revision",
"Success, clients will sync next time they connect", "Success, clients will sync next time they connect",
"Error forcing clients to sync"); "Error forcing clients to sync");
return false; return false;
@ -256,10 +282,18 @@
inv = document.getElementById("email-invite"); inv = document.getElementById("email-invite");
data = JSON.stringify({ "email": inv.value }); data = JSON.stringify({ "email": inv.value });
inv.value = ""; inv.value = "";
_post("/admin/invite/", "User invited correctly", _post("{{urlpath}}/admin/invite/", "User invited correctly",
"Error inviting user", data); "Error inviting user", data);
return false; 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() { function getFormData() {
let data = {}; let data = {};
@ -278,7 +312,7 @@
} }
function saveConfig() { function saveConfig() {
data = JSON.stringify(getFormData()); data = JSON.stringify(getFormData());
_post("/admin/config/", "Config saved correctly", _post("{{urlpath}}/admin/config/", "Config saved correctly",
"Error saving config", data); "Error saving config", data);
return false; return false;
} }
@ -286,7 +320,7 @@
var input = prompt("This will remove all user configurations, and restore the defaults and the " + 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:"); "values set by the environment. This operation could be dangerous. Type 'DELETE' to proceed:");
if (input === "DELETE") { if (input === "DELETE") {
_post("/admin/config/delete", _post("{{urlpath}}/admin/config/delete",
"Config deleted correctly", "Config deleted correctly",
"Error deleting config"); "Error deleting config");
} else { } else {
@ -296,9 +330,9 @@
return false; return false;
} }
function backupDatabase() { function backupDatabase() {
_post("/admin/config/backup_db", _post("{{urlpath}}/admin/config/backup_db",
"Backup created successfully", "Backup created successfully",
"Error creating backup"); "Error creating backup", null, false);
return false; return false;
} }
function masterCheck(check_id, inputs_query) { function masterCheck(check_id, inputs_query) {

View file

@ -3,6 +3,6 @@ Invitation accepted
<html> <html>
<p> <p>
Your invitation for <b>{{email}}</b> to join <b>{{org_name}}</b> was accepted. 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> </p>
</html> </html>

View file

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

View file

@ -3,6 +3,6 @@ Invitation to {{org_name}} confirmed
<html> <html>
<p> <p>
Your invitation to join <b>{{org_name}}</b> was confirmed. 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> </p>
</html> </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;"> <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"> <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> 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> </td>
</tr> </tr>
</table> </table>

View file

@ -9,6 +9,6 @@ New Device Logged In From {{device}}
Device Type: {{device}} Device Type: {{device}}
You can deauthorize all devices that have access to your account from the 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> </p>
</html> </html>

View file

@ -116,7 +116,7 @@ New Device Logged In From {{device}}
</tr> </tr>
<tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;"> <tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
<td class="content-block 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"> <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> </td>
</tr> </tr>
</table> </table>

View file

@ -3,7 +3,7 @@ Your master password hint
You (or someone) recently requested your master password hint. You (or someone) recently requested your master password hint.
Your hint is: "{{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. 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;"> <tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
<td class="content-block" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; margin: 0; -webkit-font-smoothing: antialiased; padding: 0 0 10px; -webkit-text-size-adjust: none;" valign="top"> <td class="content-block" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; margin: 0; -webkit-font-smoothing: antialiased; padding: 0 0 10px; -webkit-text-size-adjust: none;" valign="top">
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;" /> 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> </td>
</tr> </tr>
<tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;"> <tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">

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> <html>
<p> <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>
<p>If you did not request to create an account, you can safely ignore this email.</p> <p>If you did not request to create an account, you can safely ignore this email.</p>
</html> </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;"> <table width="100%" cellpadding="0" cellspacing="0" style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
<tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;"> <tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
<td class="content-block" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; margin: 0; -webkit-font-smoothing: antialiased; padding: 0 0 10px; -webkit-text-size-adjust: none; text-align: center;" valign="top" align="center"> <td class="content-block" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; margin: 0; -webkit-font-smoothing: antialiased; padding: 0 0 10px; -webkit-text-size-adjust: none; text-align: center;" valign="top" align="center">
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> </td>
</tr> </tr>
<tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;"> <tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">

View file

@ -2,7 +2,7 @@ Welcome
<!----------------> <!---------------->
<html> <html>
<p> <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>
<br> <br>
<a href="{{url}}/#/verify-email/?userId={{user_id}}&token={{token}}"> <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;"> <table width="100%" cellpadding="0" cellspacing="0" style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
<tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;"> <tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
<td class="content-block" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; margin: 0; -webkit-font-smoothing: antialiased; padding: 0 0 10px; -webkit-text-size-adjust: none; text-align: center;" valign="top" align="center"> <td class="content-block" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; margin: 0; -webkit-font-smoothing: antialiased; padding: 0 0 10px; -webkit-text-size-adjust: none; text-align: center;" valign="top" align="center">
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> </td>
</tr> </tr>
<tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;"> <tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">

View file

@ -109,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 // Effectively ignores, any static file route, and the alive endpoint
const LOGGED_ROUTES: [&str; 6] = [ const LOGGED_ROUTES: [&str; 6] = [
"/api", "/api",
@ -157,7 +157,10 @@ impl Fairing for BetterLogging {
} }
let uri = request.uri(); let uri = request.uri();
let uri_path = uri.path(); 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() { match uri.query() {
Some(q) => info!(target: "request", "{} {}?{}", method, uri_path, &q[..q.len().min(30)]), Some(q) => info!(target: "request", "{} {}?{}", method, uri_path, &q[..q.len().min(30)]),
None => info!(target: "request", "{} {}", method, uri_path), None => info!(target: "request", "{} {}", method, uri_path),
@ -169,8 +172,10 @@ impl Fairing for BetterLogging {
if !self.0 && request.method() == Method::Options { if !self.0 && request.method() == Method::Options {
return; return;
} }
let uri_path = request.uri().path(); // FIXME: trim_start_matches() could result in over-trimming in pathological cases;
if self.0 || LOGGED_ROUTES.iter().any(|r| uri_path.starts_with(r)) { // 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(); let status = response.status();
if let Some(ref route) = request.route() { if let Some(ref route) = request.route() {
info!(target: "response", "{} => {} {}", route, status.code, status.reason) info!(target: "response", "{} => {} {}", route, status.code, status.reason)