mirror of
https://github.com/dani-garcia/vaultwarden.git
synced 2025-09-10 18:55:57 +03:00
Compare commits
65 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
2c2276c5bb | ||
|
672a245548 | ||
|
2d2745195e | ||
|
026f9da035 | ||
|
d23d4f2c1d | ||
|
515b87755a | ||
|
d8ea3d2bfe | ||
|
ee7837d022 | ||
|
07743e490b | ||
|
9101d6e48f | ||
|
27c23b60b8 | ||
|
e7b6238f43 | ||
|
8be2ed6255 | ||
|
c9c3f07171 | ||
|
8a21c6df10 | ||
|
df71f57d86 | ||
|
60e39a9dd1 | ||
|
bc6a53b847 | ||
|
05a1137828 | ||
|
cef38bf40b | ||
|
0b13a8c4aa | ||
|
3fbd7919d8 | ||
|
5f688ff209 | ||
|
f6cfb5bf21 | ||
|
df8c9f39ac | ||
|
d7ee7caed4 | ||
|
2e300da057 | ||
|
3fb63bbe8c | ||
|
9671ed4cca | ||
|
d10ef3fd4b | ||
|
dd0b847912 | ||
|
8c34ff5d23 | ||
|
15750256e2 | ||
|
6989fc7bdb | ||
|
4923614730 | ||
|
76f38621de | ||
|
fff72889f6 | ||
|
12af32b9ea | ||
|
9add8e19eb | ||
|
5710703c50 | ||
|
1322b876e9 | ||
|
9ed2ba61c6 | ||
|
62a461ae15 | ||
|
6f7220b68e | ||
|
4859932d35 | ||
|
ee277de707 | ||
|
c11f47903a | ||
|
6a5f1613e7 | ||
|
dc36f0cb6c | ||
|
6c38026ef5 | ||
|
4c9cc9890c | ||
|
f57b407c60 | ||
|
ce0651b79c | ||
|
edc26cb1e1 | ||
|
ff759397f6 | ||
|
badd22ac3d | ||
|
6f78395ef7 | ||
|
5fb6531db8 | ||
|
eb9d5e1196 | ||
|
233b48bdad | ||
|
e22e290f67 | ||
|
ab95a69dc8 | ||
|
85c8a01f4a | ||
|
42af7c6dab | ||
|
ef551f4cc6 |
@@ -4,8 +4,13 @@
|
|||||||
## Main data folder
|
## Main data folder
|
||||||
# DATA_FOLDER=data
|
# DATA_FOLDER=data
|
||||||
|
|
||||||
## Individual folders, these override %DATA_FOLDER%
|
## Database URL
|
||||||
|
## When using SQLite, this is the path to the DB file, default to %DATA_FOLDER%/db.sqlite3
|
||||||
|
## When using MySQL, this it is the URL to the DB, including username and password:
|
||||||
|
## Format: mysql://[user[:password]@]host/database_name
|
||||||
# DATABASE_URL=data/db.sqlite3
|
# DATABASE_URL=data/db.sqlite3
|
||||||
|
|
||||||
|
## Individual folders, these override %DATA_FOLDER%
|
||||||
# RSA_KEY_FILENAME=data/rsa_key
|
# RSA_KEY_FILENAME=data/rsa_key
|
||||||
# ICON_CACHE_FOLDER=data/icon_cache
|
# ICON_CACHE_FOLDER=data/icon_cache
|
||||||
# ATTACHMENTS_FOLDER=data/attachments
|
# ATTACHMENTS_FOLDER=data/attachments
|
||||||
@@ -144,3 +149,4 @@
|
|||||||
# SMTP_SSL=true
|
# SMTP_SSL=true
|
||||||
# SMTP_USERNAME=username
|
# SMTP_USERNAME=username
|
||||||
# SMTP_PASSWORD=password
|
# SMTP_PASSWORD=password
|
||||||
|
# SMTP_AUTH_MECHANISM="Plain"
|
||||||
|
7
.hadolint.yaml
Normal file
7
.hadolint.yaml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
ignored:
|
||||||
|
# disable explicit version for apt install
|
||||||
|
- DL3008
|
||||||
|
# disable explicit version for apk install
|
||||||
|
- DL3018
|
||||||
|
trustedRegistries:
|
||||||
|
- docker.io
|
13
.travis.yml
13
.travis.yml
@@ -1,9 +1,20 @@
|
|||||||
dist: xenial
|
dist: xenial
|
||||||
|
|
||||||
|
env:
|
||||||
|
global:
|
||||||
|
- HADOLINT_VERSION=1.17.1
|
||||||
|
|
||||||
language: rust
|
language: rust
|
||||||
rust: nightly
|
rust: nightly
|
||||||
cache: cargo
|
cache: cargo
|
||||||
|
|
||||||
|
before_install:
|
||||||
|
- sudo curl -L https://github.com/hadolint/hadolint/releases/download/v$HADOLINT_VERSION/hadolint-$(uname -s)-$(uname -m) -o /usr/local/bin/hadolint
|
||||||
|
- sudo chmod +rx /usr/local/bin/hadolint
|
||||||
|
|
||||||
# Nothing to install
|
# Nothing to install
|
||||||
install: true
|
install: true
|
||||||
script: cargo build --all-features
|
script:
|
||||||
|
- git ls-files --exclude='Dockerfile*' --ignored | xargs --max-lines=1 hadolint
|
||||||
|
- cargo build --features "sqlite"
|
||||||
|
- cargo build --features "mysql"
|
||||||
|
1285
Cargo.lock
generated
1285
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
43
Cargo.toml
43
Cargo.toml
@@ -13,6 +13,8 @@ build = "build.rs"
|
|||||||
[features]
|
[features]
|
||||||
# Empty to keep compatibility, prefer to set USE_SYSLOG=true
|
# Empty to keep compatibility, prefer to set USE_SYSLOG=true
|
||||||
enable_syslog = []
|
enable_syslog = []
|
||||||
|
mysql = ["diesel/mysql", "diesel_migrations/mysql"]
|
||||||
|
sqlite = ["diesel/sqlite", "diesel_migrations/sqlite", "libsqlite3-sys"]
|
||||||
|
|
||||||
[target."cfg(not(windows))".dependencies]
|
[target."cfg(not(windows))".dependencies]
|
||||||
syslog = "4.0.1"
|
syslog = "4.0.1"
|
||||||
@@ -23,13 +25,13 @@ 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.17"
|
reqwest = "0.9.19"
|
||||||
|
|
||||||
# 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 }
|
||||||
|
|
||||||
# WebSockets library
|
# WebSockets library
|
||||||
ws = "0.8.1"
|
ws = "0.9.0"
|
||||||
|
|
||||||
# MessagePack library
|
# MessagePack library
|
||||||
rmpv = "0.4.0"
|
rmpv = "0.4.0"
|
||||||
@@ -38,20 +40,20 @@ rmpv = "0.4.0"
|
|||||||
chashmap = "2.2.2"
|
chashmap = "2.2.2"
|
||||||
|
|
||||||
# A generic serialization/deserialization framework
|
# A generic serialization/deserialization framework
|
||||||
serde = "1.0.91"
|
serde = "1.0.99"
|
||||||
serde_derive = "1.0.91"
|
serde_derive = "1.0.99"
|
||||||
serde_json = "1.0.39"
|
serde_json = "1.0.40"
|
||||||
|
|
||||||
# Logging
|
# Logging
|
||||||
log = "0.4.6"
|
log = "0.4.8"
|
||||||
fern = { version = "0.5.8", features = ["syslog-4"] }
|
fern = { version = "0.5.8", features = ["syslog-4"] }
|
||||||
|
|
||||||
# A safe, extensible ORM and Query builder
|
# A safe, extensible ORM and Query builder
|
||||||
diesel = { version = "1.4.2", features = ["sqlite", "chrono", "r2d2"] }
|
diesel = { version = "1.4.2", features = [ "chrono", "r2d2"] }
|
||||||
diesel_migrations = { version = "1.4.0", features = ["sqlite"] }
|
diesel_migrations = "1.4.0"
|
||||||
|
|
||||||
# Bundled SQLite
|
# Bundled SQLite
|
||||||
libsqlite3-sys = { version = "0.12.0", features = ["bundled"] }
|
libsqlite3-sys = { version = "0.12.0", features = ["bundled"], optional = true }
|
||||||
|
|
||||||
# Crypto library
|
# Crypto library
|
||||||
ring = "0.14.6"
|
ring = "0.14.6"
|
||||||
@@ -60,7 +62,7 @@ ring = "0.14.6"
|
|||||||
uuid = { version = "0.7.4", features = ["v4"] }
|
uuid = { version = "0.7.4", features = ["v4"] }
|
||||||
|
|
||||||
# Date and time library for Rust
|
# Date and time library for Rust
|
||||||
chrono = "0.4.6"
|
chrono = "0.4.7"
|
||||||
|
|
||||||
# TOTP library
|
# TOTP library
|
||||||
oath = "0.10.2"
|
oath = "0.10.2"
|
||||||
@@ -75,7 +77,7 @@ jsonwebtoken = "6.0.1"
|
|||||||
u2f = "0.1.6"
|
u2f = "0.1.6"
|
||||||
|
|
||||||
# Yubico Library
|
# Yubico Library
|
||||||
yubico = { version = "0.5.1", features = ["online"], default-features = false }
|
yubico = { version = "0.6.1", features = ["online", "online-tokio"], default-features = false }
|
||||||
|
|
||||||
# A `dotenv` implementation for Rust
|
# A `dotenv` implementation for Rust
|
||||||
dotenv = { version = "0.14.1", default-features = false }
|
dotenv = { version = "0.14.1", default-features = false }
|
||||||
@@ -84,24 +86,27 @@ dotenv = { version = "0.14.1", default-features = false }
|
|||||||
lazy_static = "1.3.0"
|
lazy_static = "1.3.0"
|
||||||
|
|
||||||
# More derives
|
# More derives
|
||||||
derive_more = "0.14.0"
|
derive_more = "0.15.0"
|
||||||
|
|
||||||
# Numerical libraries
|
# Numerical libraries
|
||||||
num-traits = "0.2.6"
|
num-traits = "0.2.8"
|
||||||
num-derive = "0.2.5"
|
num-derive = "0.2.5"
|
||||||
|
|
||||||
# Email libraries
|
# Email libraries
|
||||||
lettre = "0.9.1"
|
lettre = "0.9.2"
|
||||||
lettre_email = "0.9.1"
|
lettre_email = "0.9.2"
|
||||||
native-tls = "0.2.3"
|
native-tls = "0.2.3"
|
||||||
quoted_printable = "0.4.0"
|
quoted_printable = "0.4.1"
|
||||||
|
|
||||||
# Template library
|
# Template library
|
||||||
handlebars = "1.1.0"
|
handlebars = "2.0.1"
|
||||||
|
|
||||||
# For favicon extraction from main website
|
# For favicon extraction from main website
|
||||||
soup = "0.4.1"
|
soup = "0.4.1"
|
||||||
regex = "1.1.6"
|
regex = "1.2.1"
|
||||||
|
|
||||||
|
# URL encoding library
|
||||||
|
percent-encoding = "2.1.0"
|
||||||
|
|
||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
# Add support for Timestamp type
|
# Add support for Timestamp type
|
||||||
|
86
Dockerfile
86
Dockerfile
@@ -1,86 +0,0 @@
|
|||||||
# Using multistage build:
|
|
||||||
# https://docs.docker.com/develop/develop-images/multistage-build/
|
|
||||||
# https://whitfin.io/speeding-up-rust-docker-builds/
|
|
||||||
####################### VAULT BUILD IMAGE #######################
|
|
||||||
FROM alpine as vault
|
|
||||||
|
|
||||||
ENV VAULT_VERSION "v2.10.1"
|
|
||||||
|
|
||||||
ENV URL "https://github.com/dani-garcia/bw_web_builds/releases/download/$VAULT_VERSION/bw_web_$VAULT_VERSION.tar.gz"
|
|
||||||
|
|
||||||
RUN apk add --update-cache --upgrade \
|
|
||||||
curl \
|
|
||||||
tar
|
|
||||||
|
|
||||||
RUN mkdir /web-vault
|
|
||||||
WORKDIR /web-vault
|
|
||||||
|
|
||||||
RUN curl -L $URL | tar xz
|
|
||||||
RUN ls
|
|
||||||
|
|
||||||
########################## BUILD IMAGE ##########################
|
|
||||||
# We need to use the Rust build image, because
|
|
||||||
# we need the Rust compiler and Cargo tooling
|
|
||||||
FROM rust as build
|
|
||||||
|
|
||||||
# Using bundled SQLite, no need to install it
|
|
||||||
# RUN apt-get update && apt-get install -y\
|
|
||||||
# sqlite3\
|
|
||||||
# --no-install-recommends\
|
|
||||||
# && rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
# Creates a dummy project used to grab dependencies
|
|
||||||
RUN USER=root cargo new --bin app
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
# Copies over *only* your manifests and build files
|
|
||||||
COPY ./Cargo.* ./
|
|
||||||
COPY ./rust-toolchain ./rust-toolchain
|
|
||||||
COPY ./build.rs ./build.rs
|
|
||||||
|
|
||||||
# Builds your dependencies and removes the
|
|
||||||
# dummy project, except the target folder
|
|
||||||
# This folder contains the compiled dependencies
|
|
||||||
RUN cargo build --release
|
|
||||||
RUN find . -not -path "./target*" -delete
|
|
||||||
|
|
||||||
# Copies the complete project
|
|
||||||
# To avoid copying unneeded files, use .dockerignore
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
# Make sure that we actually build the project
|
|
||||||
RUN touch src/main.rs
|
|
||||||
|
|
||||||
# Builds again, this time it'll just be
|
|
||||||
# your actual source files being built
|
|
||||||
RUN cargo build --release
|
|
||||||
|
|
||||||
######################## RUNTIME IMAGE ########################
|
|
||||||
# Create a new stage with a minimal image
|
|
||||||
# because we already have a binary built
|
|
||||||
FROM debian:stretch-slim
|
|
||||||
|
|
||||||
ENV ROCKET_ENV "staging"
|
|
||||||
ENV ROCKET_PORT=80
|
|
||||||
ENV ROCKET_WORKERS=10
|
|
||||||
|
|
||||||
# Install needed libraries
|
|
||||||
RUN apt-get update && apt-get install -y\
|
|
||||||
openssl\
|
|
||||||
ca-certificates\
|
|
||||||
--no-install-recommends\
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
RUN mkdir /data
|
|
||||||
VOLUME /data
|
|
||||||
EXPOSE 80
|
|
||||||
EXPOSE 3012
|
|
||||||
|
|
||||||
# Copies the files from the context (Rocket.toml file and web-vault)
|
|
||||||
# and the binary from the "build" stage to the current stage
|
|
||||||
COPY Rocket.toml .
|
|
||||||
COPY --from=vault /web-vault ./web-vault
|
|
||||||
COPY --from=build app/target/release/bitwarden_rs .
|
|
||||||
|
|
||||||
# Configures the startup!
|
|
||||||
CMD ./bitwarden_rs
|
|
1
Dockerfile
Symbolic link
1
Dockerfile
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
docker/amd64/sqlite/Dockerfile
|
@@ -8,10 +8,18 @@ steps:
|
|||||||
echo "##vso[task.prependpath]$HOME/.cargo/bin"
|
echo "##vso[task.prependpath]$HOME/.cargo/bin"
|
||||||
displayName: 'Install Rust'
|
displayName: 'Install Rust'
|
||||||
|
|
||||||
|
- script: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y libmysql++-dev
|
||||||
|
displayName: Install libmysql
|
||||||
|
|
||||||
- script: |
|
- script: |
|
||||||
rustc -Vv
|
rustc -Vv
|
||||||
cargo -V
|
cargo -V
|
||||||
displayName: Query rust and cargo versions
|
displayName: Query rust and cargo versions
|
||||||
|
|
||||||
- script : cargo build --all-features
|
- script : cargo build --features "sqlite"
|
||||||
displayName: 'Build project'
|
displayName: 'Build project with sqlite backend'
|
||||||
|
|
||||||
|
- script : cargo build --features "mysql"
|
||||||
|
displayName: 'Build project with mysql backend'
|
||||||
|
6
build.rs
6
build.rs
@@ -1,6 +1,12 @@
|
|||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
#[cfg(all(feature = "sqlite", feature = "mysql"))]
|
||||||
|
compile_error!("Can't enable both backends");
|
||||||
|
|
||||||
|
#[cfg(not(any(feature = "sqlite", feature = "mysql")))]
|
||||||
|
compile_error!("You need to enable one DB backend. To build with previous defaults do: cargo build --features sqlite");
|
||||||
|
|
||||||
read_git_info().ok();
|
read_git_info().ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -2,29 +2,35 @@
|
|||||||
# 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 as vault
|
FROM alpine:3.10 as vault
|
||||||
|
|
||||||
ENV VAULT_VERSION "v2.10.1"
|
ENV VAULT_VERSION "v2.11.0"
|
||||||
|
|
||||||
ENV URL "https://github.com/dani-garcia/bw_web_builds/releases/download/$VAULT_VERSION/bw_web_$VAULT_VERSION.tar.gz"
|
ENV URL "https://github.com/dani-garcia/bw_web_builds/releases/download/$VAULT_VERSION/bw_web_$VAULT_VERSION.tar.gz"
|
||||||
|
|
||||||
RUN apk add --update-cache --upgrade \
|
RUN apk add --no-cache --upgrade \
|
||||||
curl \
|
curl \
|
||||||
tar
|
tar
|
||||||
|
|
||||||
RUN mkdir /web-vault
|
RUN mkdir /web-vault
|
||||||
WORKDIR /web-vault
|
WORKDIR /web-vault
|
||||||
|
|
||||||
|
SHELL ["/bin/ash", "-eo", "pipefail", "-c"]
|
||||||
|
|
||||||
RUN curl -L $URL | tar xz
|
RUN curl -L $URL | tar xz
|
||||||
RUN ls
|
RUN ls
|
||||||
|
|
||||||
########################## BUILD IMAGE ##########################
|
########################## BUILD IMAGE ##########################
|
||||||
# We need to use the Rust build image, because
|
# We need to use the Rust build image, because
|
||||||
# we need the Rust compiler and Cargo tooling
|
# we need the Rust compiler and Cargo tooling
|
||||||
FROM rust as build
|
FROM rust:1.36 as build
|
||||||
|
|
||||||
|
# set mysql backend
|
||||||
|
ARG DB=mysql
|
||||||
|
|
||||||
RUN apt-get update \
|
RUN apt-get update \
|
||||||
&& apt-get install -y \
|
&& apt-get install -y \
|
||||||
|
--no-install-recommends \
|
||||||
gcc-aarch64-linux-gnu \
|
gcc-aarch64-linux-gnu \
|
||||||
&& mkdir -p ~/.cargo \
|
&& mkdir -p ~/.cargo \
|
||||||
&& echo '[target.aarch64-unknown-linux-gnu]' >> ~/.cargo/config \
|
&& echo '[target.aarch64-unknown-linux-gnu]' >> ~/.cargo/config \
|
||||||
@@ -41,8 +47,10 @@ RUN sed 's/^deb/deb-src/' /etc/apt/sources.list > \
|
|||||||
&& dpkg --add-architecture arm64 \
|
&& dpkg --add-architecture arm64 \
|
||||||
&& apt-get update \
|
&& apt-get update \
|
||||||
&& apt-get install -y \
|
&& apt-get install -y \
|
||||||
|
--no-install-recommends \
|
||||||
libssl-dev:arm64 \
|
libssl-dev:arm64 \
|
||||||
libc6-dev:arm64
|
libc6-dev:arm64 \
|
||||||
|
libmariadb-dev:arm64
|
||||||
|
|
||||||
ENV CC_aarch64_unknown_linux_gnu="/usr/bin/aarch64-linux-gnu-gcc"
|
ENV CC_aarch64_unknown_linux_gnu="/usr/bin/aarch64-linux-gnu-gcc"
|
||||||
ENV CROSS_COMPILE="1"
|
ENV CROSS_COMPILE="1"
|
||||||
@@ -55,7 +63,7 @@ COPY . .
|
|||||||
|
|
||||||
# Build
|
# Build
|
||||||
RUN rustup target add aarch64-unknown-linux-gnu
|
RUN rustup target add aarch64-unknown-linux-gnu
|
||||||
RUN cargo build --release --target=aarch64-unknown-linux-gnu -v
|
RUN cargo build --features ${DB} --release --target=aarch64-unknown-linux-gnu -v
|
||||||
|
|
||||||
######################## RUNTIME IMAGE ########################
|
######################## RUNTIME IMAGE ########################
|
||||||
# Create a new stage with a minimal image
|
# Create a new stage with a minimal image
|
||||||
@@ -69,10 +77,11 @@ ENV ROCKET_WORKERS=10
|
|||||||
RUN [ "cross-build-start" ]
|
RUN [ "cross-build-start" ]
|
||||||
|
|
||||||
# Install needed libraries
|
# Install needed libraries
|
||||||
RUN apt-get update && apt-get install -y\
|
RUN apt-get update && apt-get install -y \
|
||||||
openssl\
|
--no-install-recommends \
|
||||||
ca-certificates\
|
openssl \
|
||||||
--no-install-recommends\
|
ca-certificates \
|
||||||
|
libmariadbclient-dev \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
RUN mkdir /data
|
RUN mkdir /data
|
||||||
@@ -89,4 +98,4 @@ COPY --from=vault /web-vault ./web-vault
|
|||||||
COPY --from=build /app/target/aarch64-unknown-linux-gnu/release/bitwarden_rs .
|
COPY --from=build /app/target/aarch64-unknown-linux-gnu/release/bitwarden_rs .
|
||||||
|
|
||||||
# Configures the startup!
|
# Configures the startup!
|
||||||
CMD ./bitwarden_rs
|
CMD ["./bitwarden_rs"]
|
101
docker/aarch64/sqlite/Dockerfile
Normal file
101
docker/aarch64/sqlite/Dockerfile
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
# Using multistage build:
|
||||||
|
# https://docs.docker.com/develop/develop-images/multistage-build/
|
||||||
|
# https://whitfin.io/speeding-up-rust-docker-builds/
|
||||||
|
####################### VAULT BUILD IMAGE #######################
|
||||||
|
FROM alpine:3.10 as vault
|
||||||
|
|
||||||
|
ENV VAULT_VERSION "v2.11.0"
|
||||||
|
|
||||||
|
ENV URL "https://github.com/dani-garcia/bw_web_builds/releases/download/$VAULT_VERSION/bw_web_$VAULT_VERSION.tar.gz"
|
||||||
|
|
||||||
|
RUN apk add --no-cache --upgrade \
|
||||||
|
curl \
|
||||||
|
tar
|
||||||
|
|
||||||
|
RUN mkdir /web-vault
|
||||||
|
WORKDIR /web-vault
|
||||||
|
|
||||||
|
SHELL ["/bin/ash", "-eo", "pipefail", "-c"]
|
||||||
|
|
||||||
|
RUN curl -L $URL | tar xz
|
||||||
|
RUN ls
|
||||||
|
|
||||||
|
########################## BUILD IMAGE ##########################
|
||||||
|
# We need to use the Rust build image, because
|
||||||
|
# we need the Rust compiler and Cargo tooling
|
||||||
|
FROM rust:1.36 as build
|
||||||
|
|
||||||
|
# set sqlite as default for DB ARG for backward comaptibility
|
||||||
|
ARG DB=sqlite
|
||||||
|
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install -y \
|
||||||
|
--no-install-recommends \
|
||||||
|
gcc-aarch64-linux-gnu \
|
||||||
|
&& mkdir -p ~/.cargo \
|
||||||
|
&& echo '[target.aarch64-unknown-linux-gnu]' >> ~/.cargo/config \
|
||||||
|
&& echo 'linker = "aarch64-linux-gnu-gcc"' >> ~/.cargo/config
|
||||||
|
|
||||||
|
ENV CARGO_HOME "/root/.cargo"
|
||||||
|
ENV USER "root"
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Prepare openssl arm64 libs
|
||||||
|
RUN sed 's/^deb/deb-src/' /etc/apt/sources.list > \
|
||||||
|
/etc/apt/sources.list.d/deb-src.list \
|
||||||
|
&& dpkg --add-architecture arm64 \
|
||||||
|
&& apt-get update \
|
||||||
|
&& apt-get install -y \
|
||||||
|
--no-install-recommends \
|
||||||
|
libssl-dev:arm64 \
|
||||||
|
libc6-dev:arm64 \
|
||||||
|
libmariadb-dev:arm64
|
||||||
|
|
||||||
|
ENV CC_aarch64_unknown_linux_gnu="/usr/bin/aarch64-linux-gnu-gcc"
|
||||||
|
ENV CROSS_COMPILE="1"
|
||||||
|
ENV OPENSSL_INCLUDE_DIR="/usr/include/aarch64-linux-gnu"
|
||||||
|
ENV OPENSSL_LIB_DIR="/usr/lib/aarch64-linux-gnu"
|
||||||
|
|
||||||
|
# Copies the complete project
|
||||||
|
# To avoid copying unneeded files, use .dockerignore
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Build
|
||||||
|
RUN rustup target add aarch64-unknown-linux-gnu
|
||||||
|
RUN cargo build --features ${DB} --release --target=aarch64-unknown-linux-gnu -v
|
||||||
|
|
||||||
|
######################## RUNTIME IMAGE ########################
|
||||||
|
# Create a new stage with a minimal image
|
||||||
|
# because we already have a binary built
|
||||||
|
FROM balenalib/aarch64-debian:stretch
|
||||||
|
|
||||||
|
ENV ROCKET_ENV "staging"
|
||||||
|
ENV ROCKET_PORT=80
|
||||||
|
ENV ROCKET_WORKERS=10
|
||||||
|
|
||||||
|
RUN [ "cross-build-start" ]
|
||||||
|
|
||||||
|
# Install needed libraries
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
|
--no-install-recommends \
|
||||||
|
openssl \
|
||||||
|
ca-certificates \
|
||||||
|
libmariadbclient-dev \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
RUN mkdir /data
|
||||||
|
|
||||||
|
RUN [ "cross-build-end" ]
|
||||||
|
|
||||||
|
VOLUME /data
|
||||||
|
EXPOSE 80
|
||||||
|
|
||||||
|
# Copies the files from the context (Rocket.toml file and web-vault)
|
||||||
|
# and the binary from the "build" stage to the current stage
|
||||||
|
COPY Rocket.toml .
|
||||||
|
COPY --from=vault /web-vault ./web-vault
|
||||||
|
COPY --from=build /app/target/aarch64-unknown-linux-gnu/release/bitwarden_rs .
|
||||||
|
|
||||||
|
# Configures the startup!
|
||||||
|
CMD ["./bitwarden_rs"]
|
98
docker/amd64/mysql/Dockerfile
Normal file
98
docker/amd64/mysql/Dockerfile
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
# Using multistage build:
|
||||||
|
# https://docs.docker.com/develop/develop-images/multistage-build/
|
||||||
|
# https://whitfin.io/speeding-up-rust-docker-builds/
|
||||||
|
####################### VAULT BUILD IMAGE #######################
|
||||||
|
FROM alpine:3.10 as vault
|
||||||
|
|
||||||
|
ENV VAULT_VERSION "v2.11.0"
|
||||||
|
|
||||||
|
ENV URL "https://github.com/dani-garcia/bw_web_builds/releases/download/$VAULT_VERSION/bw_web_$VAULT_VERSION.tar.gz"
|
||||||
|
|
||||||
|
RUN apk add --no-cache --upgrade \
|
||||||
|
curl \
|
||||||
|
tar
|
||||||
|
|
||||||
|
RUN mkdir /web-vault
|
||||||
|
WORKDIR /web-vault
|
||||||
|
|
||||||
|
SHELL ["/bin/ash", "-eo", "pipefail", "-c"]
|
||||||
|
|
||||||
|
RUN curl -L $URL | tar xz
|
||||||
|
RUN ls
|
||||||
|
|
||||||
|
########################## BUILD IMAGE ##########################
|
||||||
|
# We need to use the Rust build image, because
|
||||||
|
# we need the Rust compiler and Cargo tooling
|
||||||
|
FROM rust:1.36 as build
|
||||||
|
|
||||||
|
# set mysql backend
|
||||||
|
ARG DB=mysql
|
||||||
|
|
||||||
|
# Using bundled SQLite, no need to install it
|
||||||
|
# RUN apt-get update && apt-get install -y\
|
||||||
|
# --no-install-recommends \
|
||||||
|
# sqlite3\
|
||||||
|
# && rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Install MySQL package
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
|
--no-install-recommends \
|
||||||
|
libmariadb-dev \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Creates a dummy project used to grab dependencies
|
||||||
|
RUN USER=root cargo new --bin app
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copies over *only* your manifests and build files
|
||||||
|
COPY ./Cargo.* ./
|
||||||
|
COPY ./rust-toolchain ./rust-toolchain
|
||||||
|
COPY ./build.rs ./build.rs
|
||||||
|
|
||||||
|
# Builds your dependencies and removes the
|
||||||
|
# dummy project, except the target folder
|
||||||
|
# This folder contains the compiled dependencies
|
||||||
|
RUN cargo build --features ${DB} --release
|
||||||
|
RUN find . -not -path "./target*" -delete
|
||||||
|
|
||||||
|
# Copies the complete project
|
||||||
|
# To avoid copying unneeded files, use .dockerignore
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Make sure that we actually build the project
|
||||||
|
RUN touch src/main.rs
|
||||||
|
|
||||||
|
# Builds again, this time it'll just be
|
||||||
|
# your actual source files being built
|
||||||
|
RUN cargo build --features ${DB} --release
|
||||||
|
|
||||||
|
######################## RUNTIME IMAGE ########################
|
||||||
|
# Create a new stage with a minimal image
|
||||||
|
# because we already have a binary built
|
||||||
|
FROM debian:stretch-slim
|
||||||
|
|
||||||
|
ENV ROCKET_ENV "staging"
|
||||||
|
ENV ROCKET_PORT=80
|
||||||
|
ENV ROCKET_WORKERS=10
|
||||||
|
|
||||||
|
# Install needed libraries
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
|
--no-install-recommends \
|
||||||
|
openssl \
|
||||||
|
ca-certificates \
|
||||||
|
libmariadbclient-dev \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
RUN mkdir /data
|
||||||
|
VOLUME /data
|
||||||
|
EXPOSE 80
|
||||||
|
EXPOSE 3012
|
||||||
|
|
||||||
|
# Copies the files from the context (Rocket.toml file and web-vault)
|
||||||
|
# and the binary from the "build" stage to the current stage
|
||||||
|
COPY Rocket.toml .
|
||||||
|
COPY --from=vault /web-vault ./web-vault
|
||||||
|
COPY --from=build app/target/release/bitwarden_rs .
|
||||||
|
|
||||||
|
# Configures the startup!
|
||||||
|
CMD ["./bitwarden_rs"]
|
@@ -2,28 +2,39 @@
|
|||||||
# 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 as vault
|
FROM alpine:3.10 as vault
|
||||||
|
|
||||||
ENV VAULT_VERSION "v2.10.1"
|
ENV VAULT_VERSION "v2.11.0"
|
||||||
|
|
||||||
ENV URL "https://github.com/dani-garcia/bw_web_builds/releases/download/$VAULT_VERSION/bw_web_$VAULT_VERSION.tar.gz"
|
ENV URL "https://github.com/dani-garcia/bw_web_builds/releases/download/$VAULT_VERSION/bw_web_$VAULT_VERSION.tar.gz"
|
||||||
|
|
||||||
RUN apk add --update-cache --upgrade \
|
RUN apk add --no-cache --upgrade \
|
||||||
curl \
|
curl \
|
||||||
tar
|
tar
|
||||||
|
|
||||||
RUN mkdir /web-vault
|
RUN mkdir /web-vault
|
||||||
WORKDIR /web-vault
|
WORKDIR /web-vault
|
||||||
|
|
||||||
|
SHELL ["/bin/ash", "-eo", "pipefail", "-c"]
|
||||||
|
|
||||||
RUN curl -L $URL | tar xz
|
RUN curl -L $URL | tar xz
|
||||||
RUN ls
|
RUN ls
|
||||||
|
|
||||||
########################## BUILD IMAGE ##########################
|
########################## BUILD IMAGE ##########################
|
||||||
# Musl build image for statically compiled binary
|
# Musl build image for statically compiled binary
|
||||||
FROM clux/muslrust:nightly-2018-12-01 as build
|
FROM clux/muslrust:nightly-2019-07-08 as build
|
||||||
|
|
||||||
|
# set mysql backend
|
||||||
|
ARG DB=mysql
|
||||||
|
|
||||||
ENV USER "root"
|
ENV USER "root"
|
||||||
|
|
||||||
|
# Install needed libraries
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
|
--no-install-recommends \
|
||||||
|
libmysqlclient-dev \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# Copies the complete project
|
# Copies the complete project
|
||||||
@@ -32,13 +43,16 @@ COPY . .
|
|||||||
|
|
||||||
RUN rustup target add x86_64-unknown-linux-musl
|
RUN rustup target add x86_64-unknown-linux-musl
|
||||||
|
|
||||||
|
# Make sure that we actually build the project
|
||||||
|
RUN touch src/main.rs
|
||||||
|
|
||||||
# Build
|
# Build
|
||||||
RUN cargo build --release
|
RUN cargo build --features ${DB} --release
|
||||||
|
|
||||||
######################## RUNTIME IMAGE ########################
|
######################## RUNTIME IMAGE ########################
|
||||||
# Create a new stage with a minimal image
|
# Create a new stage with a minimal image
|
||||||
# because we already have a binary built
|
# because we already have a binary built
|
||||||
FROM alpine:3.9
|
FROM alpine:3.10
|
||||||
|
|
||||||
ENV ROCKET_ENV "staging"
|
ENV ROCKET_ENV "staging"
|
||||||
ENV ROCKET_PORT=80
|
ENV ROCKET_PORT=80
|
||||||
@@ -46,10 +60,10 @@ ENV ROCKET_WORKERS=10
|
|||||||
ENV SSL_CERT_DIR=/etc/ssl/certs
|
ENV SSL_CERT_DIR=/etc/ssl/certs
|
||||||
|
|
||||||
# Install needed libraries
|
# Install needed libraries
|
||||||
RUN apk add \
|
RUN apk add --no-cache \
|
||||||
openssl\
|
openssl \
|
||||||
ca-certificates \
|
mariadb-connector-c \
|
||||||
&& rm /var/cache/apk/*
|
ca-certificates
|
||||||
|
|
||||||
RUN mkdir /data
|
RUN mkdir /data
|
||||||
VOLUME /data
|
VOLUME /data
|
||||||
@@ -63,4 +77,4 @@ COPY --from=vault /web-vault ./web-vault
|
|||||||
COPY --from=build /app/target/x86_64-unknown-linux-musl/release/bitwarden_rs .
|
COPY --from=build /app/target/x86_64-unknown-linux-musl/release/bitwarden_rs .
|
||||||
|
|
||||||
# Configures the startup!
|
# Configures the startup!
|
||||||
CMD ./bitwarden_rs
|
CMD ["./bitwarden_rs"]
|
98
docker/amd64/sqlite/Dockerfile
Normal file
98
docker/amd64/sqlite/Dockerfile
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
# Using multistage build:
|
||||||
|
# https://docs.docker.com/develop/develop-images/multistage-build/
|
||||||
|
# https://whitfin.io/speeding-up-rust-docker-builds/
|
||||||
|
####################### VAULT BUILD IMAGE #######################
|
||||||
|
FROM alpine:3.10 as vault
|
||||||
|
|
||||||
|
ENV VAULT_VERSION "v2.11.0"
|
||||||
|
|
||||||
|
ENV URL "https://github.com/dani-garcia/bw_web_builds/releases/download/$VAULT_VERSION/bw_web_$VAULT_VERSION.tar.gz"
|
||||||
|
|
||||||
|
RUN apk add --no-cache --upgrade \
|
||||||
|
curl \
|
||||||
|
tar
|
||||||
|
|
||||||
|
RUN mkdir /web-vault
|
||||||
|
WORKDIR /web-vault
|
||||||
|
|
||||||
|
SHELL ["/bin/ash", "-eo", "pipefail", "-c"]
|
||||||
|
|
||||||
|
RUN curl -L $URL | tar xz
|
||||||
|
RUN ls
|
||||||
|
|
||||||
|
########################## BUILD IMAGE ##########################
|
||||||
|
# We need to use the Rust build image, because
|
||||||
|
# we need the Rust compiler and Cargo tooling
|
||||||
|
FROM rust:1.36 as build
|
||||||
|
|
||||||
|
# set sqlite as default for DB ARG for backward comaptibility
|
||||||
|
ARG DB=sqlite
|
||||||
|
|
||||||
|
# Using bundled SQLite, no need to install it
|
||||||
|
# RUN apt-get update && apt-get install -y\
|
||||||
|
# --no-install-recommends \
|
||||||
|
# sqlite3 \
|
||||||
|
# && rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Install MySQL package
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
|
--no-install-recommends \
|
||||||
|
libmariadb-dev \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Creates a dummy project used to grab dependencies
|
||||||
|
RUN USER=root cargo new --bin app
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copies over *only* your manifests and build files
|
||||||
|
COPY ./Cargo.* ./
|
||||||
|
COPY ./rust-toolchain ./rust-toolchain
|
||||||
|
COPY ./build.rs ./build.rs
|
||||||
|
|
||||||
|
# Builds your dependencies and removes the
|
||||||
|
# dummy project, except the target folder
|
||||||
|
# This folder contains the compiled dependencies
|
||||||
|
RUN cargo build --features ${DB} --release
|
||||||
|
RUN find . -not -path "./target*" -delete
|
||||||
|
|
||||||
|
# Copies the complete project
|
||||||
|
# To avoid copying unneeded files, use .dockerignore
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Make sure that we actually build the project
|
||||||
|
RUN touch src/main.rs
|
||||||
|
|
||||||
|
# Builds again, this time it'll just be
|
||||||
|
# your actual source files being built
|
||||||
|
RUN cargo build --features ${DB} --release
|
||||||
|
|
||||||
|
######################## RUNTIME IMAGE ########################
|
||||||
|
# Create a new stage with a minimal image
|
||||||
|
# because we already have a binary built
|
||||||
|
FROM debian:stretch-slim
|
||||||
|
|
||||||
|
ENV ROCKET_ENV "staging"
|
||||||
|
ENV ROCKET_PORT=80
|
||||||
|
ENV ROCKET_WORKERS=10
|
||||||
|
|
||||||
|
# Install needed libraries
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
|
--no-install-recommends \
|
||||||
|
openssl \
|
||||||
|
ca-certificates \
|
||||||
|
libmariadbclient-dev \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
RUN mkdir /data
|
||||||
|
VOLUME /data
|
||||||
|
EXPOSE 80
|
||||||
|
EXPOSE 3012
|
||||||
|
|
||||||
|
# Copies the files from the context (Rocket.toml file and web-vault)
|
||||||
|
# and the binary from the "build" stage to the current stage
|
||||||
|
COPY Rocket.toml .
|
||||||
|
COPY --from=vault /web-vault ./web-vault
|
||||||
|
COPY --from=build app/target/release/bitwarden_rs .
|
||||||
|
|
||||||
|
# Configures the startup!
|
||||||
|
CMD ["./bitwarden_rs"]
|
80
docker/amd64/sqlite/Dockerfile.alpine
Normal file
80
docker/amd64/sqlite/Dockerfile.alpine
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
# Using multistage build:
|
||||||
|
# https://docs.docker.com/develop/develop-images/multistage-build/
|
||||||
|
# https://whitfin.io/speeding-up-rust-docker-builds/
|
||||||
|
####################### VAULT BUILD IMAGE #######################
|
||||||
|
FROM alpine:3.10 as vault
|
||||||
|
|
||||||
|
ENV VAULT_VERSION "v2.11.0"
|
||||||
|
|
||||||
|
ENV URL "https://github.com/dani-garcia/bw_web_builds/releases/download/$VAULT_VERSION/bw_web_$VAULT_VERSION.tar.gz"
|
||||||
|
|
||||||
|
RUN apk add --no-cache --upgrade \
|
||||||
|
curl \
|
||||||
|
tar
|
||||||
|
|
||||||
|
RUN mkdir /web-vault
|
||||||
|
WORKDIR /web-vault
|
||||||
|
|
||||||
|
SHELL ["/bin/ash", "-eo", "pipefail", "-c"]
|
||||||
|
|
||||||
|
RUN curl -L $URL | tar xz
|
||||||
|
RUN ls
|
||||||
|
|
||||||
|
########################## BUILD IMAGE ##########################
|
||||||
|
# Musl build image for statically compiled binary
|
||||||
|
FROM clux/muslrust:nightly-2019-07-08 as build
|
||||||
|
|
||||||
|
# set sqlite as default for DB ARG for backward comaptibility
|
||||||
|
ARG DB=sqlite
|
||||||
|
|
||||||
|
ENV USER "root"
|
||||||
|
|
||||||
|
# Install needed libraries
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
|
--no-install-recommends \
|
||||||
|
libmysqlclient-dev \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copies the complete project
|
||||||
|
# To avoid copying unneeded files, use .dockerignore
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
RUN rustup target add x86_64-unknown-linux-musl
|
||||||
|
|
||||||
|
# Make sure that we actually build the project
|
||||||
|
RUN touch src/main.rs
|
||||||
|
|
||||||
|
# Build
|
||||||
|
RUN cargo build --features ${DB} --release
|
||||||
|
|
||||||
|
######################## RUNTIME IMAGE ########################
|
||||||
|
# Create a new stage with a minimal image
|
||||||
|
# because we already have a binary built
|
||||||
|
FROM alpine:3.10
|
||||||
|
|
||||||
|
ENV ROCKET_ENV "staging"
|
||||||
|
ENV ROCKET_PORT=80
|
||||||
|
ENV ROCKET_WORKERS=10
|
||||||
|
ENV SSL_CERT_DIR=/etc/ssl/certs
|
||||||
|
|
||||||
|
# Install needed libraries
|
||||||
|
RUN apk add --no-cache \
|
||||||
|
openssl \
|
||||||
|
mariadb-connector-c \
|
||||||
|
ca-certificates
|
||||||
|
|
||||||
|
RUN mkdir /data
|
||||||
|
VOLUME /data
|
||||||
|
EXPOSE 80
|
||||||
|
EXPOSE 3012
|
||||||
|
|
||||||
|
# Copies the files from the context (Rocket.toml file and web-vault)
|
||||||
|
# and the binary from the "build" stage to the current stage
|
||||||
|
COPY Rocket.toml .
|
||||||
|
COPY --from=vault /web-vault ./web-vault
|
||||||
|
COPY --from=build /app/target/x86_64-unknown-linux-musl/release/bitwarden_rs .
|
||||||
|
|
||||||
|
# Configures the startup!
|
||||||
|
CMD ["./bitwarden_rs"]
|
@@ -2,29 +2,35 @@
|
|||||||
# 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 as vault
|
FROM alpine:3.10 as vault
|
||||||
|
|
||||||
ENV VAULT_VERSION "v2.10.1"
|
ENV VAULT_VERSION "v2.11.0"
|
||||||
|
|
||||||
ENV URL "https://github.com/dani-garcia/bw_web_builds/releases/download/$VAULT_VERSION/bw_web_$VAULT_VERSION.tar.gz"
|
ENV URL "https://github.com/dani-garcia/bw_web_builds/releases/download/$VAULT_VERSION/bw_web_$VAULT_VERSION.tar.gz"
|
||||||
|
|
||||||
RUN apk add --update-cache --upgrade \
|
RUN apk add --no-cache --upgrade \
|
||||||
curl \
|
curl \
|
||||||
tar
|
tar
|
||||||
|
|
||||||
RUN mkdir /web-vault
|
RUN mkdir /web-vault
|
||||||
WORKDIR /web-vault
|
WORKDIR /web-vault
|
||||||
|
|
||||||
|
SHELL ["/bin/ash", "-eo", "pipefail", "-c"]
|
||||||
|
|
||||||
RUN curl -L $URL | tar xz
|
RUN curl -L $URL | tar xz
|
||||||
RUN ls
|
RUN ls
|
||||||
|
|
||||||
########################## BUILD IMAGE ##########################
|
########################## BUILD IMAGE ##########################
|
||||||
# We need to use the Rust build image, because
|
# We need to use the Rust build image, because
|
||||||
# we need the Rust compiler and Cargo tooling
|
# we need the Rust compiler and Cargo tooling
|
||||||
FROM rust as build
|
FROM rust:1.36 as build
|
||||||
|
|
||||||
|
# set mysql backend
|
||||||
|
ARG DB=mysql
|
||||||
|
|
||||||
RUN apt-get update \
|
RUN apt-get update \
|
||||||
&& apt-get install -y \
|
&& apt-get install -y \
|
||||||
|
--no-install-recommends \
|
||||||
gcc-arm-linux-gnueabi \
|
gcc-arm-linux-gnueabi \
|
||||||
&& mkdir -p ~/.cargo \
|
&& mkdir -p ~/.cargo \
|
||||||
&& echo '[target.arm-unknown-linux-gnueabi]' >> ~/.cargo/config \
|
&& echo '[target.arm-unknown-linux-gnueabi]' >> ~/.cargo/config \
|
||||||
@@ -41,8 +47,10 @@ RUN sed 's/^deb/deb-src/' /etc/apt/sources.list > \
|
|||||||
&& dpkg --add-architecture armel \
|
&& dpkg --add-architecture armel \
|
||||||
&& apt-get update \
|
&& apt-get update \
|
||||||
&& apt-get install -y \
|
&& apt-get install -y \
|
||||||
|
--no-install-recommends \
|
||||||
libssl-dev:armel \
|
libssl-dev:armel \
|
||||||
libc6-dev:armel
|
libc6-dev:armel \
|
||||||
|
libmariadb-dev:armel
|
||||||
|
|
||||||
ENV CC_arm_unknown_linux_gnueabi="/usr/bin/arm-linux-gnueabi-gcc"
|
ENV CC_arm_unknown_linux_gnueabi="/usr/bin/arm-linux-gnueabi-gcc"
|
||||||
ENV CROSS_COMPILE="1"
|
ENV CROSS_COMPILE="1"
|
||||||
@@ -55,7 +63,7 @@ COPY . .
|
|||||||
|
|
||||||
# Build
|
# Build
|
||||||
RUN rustup target add arm-unknown-linux-gnueabi
|
RUN rustup target add arm-unknown-linux-gnueabi
|
||||||
RUN cargo build --release --target=arm-unknown-linux-gnueabi -v
|
RUN cargo build --features ${DB} --release --target=arm-unknown-linux-gnueabi -v
|
||||||
|
|
||||||
######################## RUNTIME IMAGE ########################
|
######################## RUNTIME IMAGE ########################
|
||||||
# Create a new stage with a minimal image
|
# Create a new stage with a minimal image
|
||||||
@@ -69,12 +77,12 @@ ENV ROCKET_WORKERS=10
|
|||||||
RUN [ "cross-build-start" ]
|
RUN [ "cross-build-start" ]
|
||||||
|
|
||||||
# Install needed libraries
|
# Install needed libraries
|
||||||
RUN apt-get update && apt-get install -y\
|
RUN apt-get update && apt-get install -y \
|
||||||
openssl\
|
--no-install-recommends \
|
||||||
ca-certificates\
|
openssl \
|
||||||
--no-install-recommends\
|
ca-certificates \
|
||||||
&& ln -s /lib/ld-linux-armhf.so.3 /lib/ld-linux.so.3\
|
libmariadbclient-dev \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
RUN mkdir /data
|
RUN mkdir /data
|
||||||
|
|
||||||
@@ -90,4 +98,4 @@ COPY --from=vault /web-vault ./web-vault
|
|||||||
COPY --from=build /app/target/arm-unknown-linux-gnueabi/release/bitwarden_rs .
|
COPY --from=build /app/target/arm-unknown-linux-gnueabi/release/bitwarden_rs .
|
||||||
|
|
||||||
# Configures the startup!
|
# Configures the startup!
|
||||||
CMD ./bitwarden_rs
|
CMD ["./bitwarden_rs"]
|
101
docker/armv6/sqlite/Dockerfile
Normal file
101
docker/armv6/sqlite/Dockerfile
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
# Using multistage build:
|
||||||
|
# https://docs.docker.com/develop/develop-images/multistage-build/
|
||||||
|
# https://whitfin.io/speeding-up-rust-docker-builds/
|
||||||
|
####################### VAULT BUILD IMAGE #######################
|
||||||
|
FROM alpine:3.10 as vault
|
||||||
|
|
||||||
|
ENV VAULT_VERSION "v2.11.0"
|
||||||
|
|
||||||
|
ENV URL "https://github.com/dani-garcia/bw_web_builds/releases/download/$VAULT_VERSION/bw_web_$VAULT_VERSION.tar.gz"
|
||||||
|
|
||||||
|
RUN apk add --no-cache --upgrade \
|
||||||
|
curl \
|
||||||
|
tar
|
||||||
|
|
||||||
|
RUN mkdir /web-vault
|
||||||
|
WORKDIR /web-vault
|
||||||
|
|
||||||
|
SHELL ["/bin/ash", "-eo", "pipefail", "-c"]
|
||||||
|
|
||||||
|
RUN curl -L $URL | tar xz
|
||||||
|
RUN ls
|
||||||
|
|
||||||
|
########################## BUILD IMAGE ##########################
|
||||||
|
# We need to use the Rust build image, because
|
||||||
|
# we need the Rust compiler and Cargo tooling
|
||||||
|
FROM rust:1.36 as build
|
||||||
|
|
||||||
|
# set sqlite as default for DB ARG for backward comaptibility
|
||||||
|
ARG DB=sqlite
|
||||||
|
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install -y \
|
||||||
|
--no-install-recommends \
|
||||||
|
gcc-arm-linux-gnueabi \
|
||||||
|
&& mkdir -p ~/.cargo \
|
||||||
|
&& echo '[target.arm-unknown-linux-gnueabi]' >> ~/.cargo/config \
|
||||||
|
&& echo 'linker = "arm-linux-gnueabi-gcc"' >> ~/.cargo/config
|
||||||
|
|
||||||
|
ENV CARGO_HOME "/root/.cargo"
|
||||||
|
ENV USER "root"
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Prepare openssl armel libs
|
||||||
|
RUN sed 's/^deb/deb-src/' /etc/apt/sources.list > \
|
||||||
|
/etc/apt/sources.list.d/deb-src.list \
|
||||||
|
&& dpkg --add-architecture armel \
|
||||||
|
&& apt-get update \
|
||||||
|
&& apt-get install -y \
|
||||||
|
--no-install-recommends \
|
||||||
|
libssl-dev:armel \
|
||||||
|
libc6-dev:armel \
|
||||||
|
libmariadb-dev:armel
|
||||||
|
|
||||||
|
ENV CC_arm_unknown_linux_gnueabi="/usr/bin/arm-linux-gnueabi-gcc"
|
||||||
|
ENV CROSS_COMPILE="1"
|
||||||
|
ENV OPENSSL_INCLUDE_DIR="/usr/include/arm-linux-gnueabi"
|
||||||
|
ENV OPENSSL_LIB_DIR="/usr/lib/arm-linux-gnueabi"
|
||||||
|
|
||||||
|
# Copies the complete project
|
||||||
|
# To avoid copying unneeded files, use .dockerignore
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Build
|
||||||
|
RUN rustup target add arm-unknown-linux-gnueabi
|
||||||
|
RUN cargo build --features ${DB} --release --target=arm-unknown-linux-gnueabi -v
|
||||||
|
|
||||||
|
######################## RUNTIME IMAGE ########################
|
||||||
|
# Create a new stage with a minimal image
|
||||||
|
# because we already have a binary built
|
||||||
|
FROM balenalib/rpi-debian:stretch
|
||||||
|
|
||||||
|
ENV ROCKET_ENV "staging"
|
||||||
|
ENV ROCKET_PORT=80
|
||||||
|
ENV ROCKET_WORKERS=10
|
||||||
|
|
||||||
|
RUN [ "cross-build-start" ]
|
||||||
|
|
||||||
|
# Install needed libraries
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
|
--no-install-recommends \
|
||||||
|
openssl \
|
||||||
|
ca-certificates \
|
||||||
|
libmariadbclient-dev \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
RUN mkdir /data
|
||||||
|
|
||||||
|
RUN [ "cross-build-end" ]
|
||||||
|
|
||||||
|
VOLUME /data
|
||||||
|
EXPOSE 80
|
||||||
|
|
||||||
|
# Copies the files from the context (Rocket.toml file and web-vault)
|
||||||
|
# and the binary from the "build" stage to the current stage
|
||||||
|
COPY Rocket.toml .
|
||||||
|
COPY --from=vault /web-vault ./web-vault
|
||||||
|
COPY --from=build /app/target/arm-unknown-linux-gnueabi/release/bitwarden_rs .
|
||||||
|
|
||||||
|
# Configures the startup!
|
||||||
|
CMD ["./bitwarden_rs"]
|
@@ -2,29 +2,35 @@
|
|||||||
# 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 as vault
|
FROM alpine:3.10 as vault
|
||||||
|
|
||||||
ENV VAULT_VERSION "v2.10.1"
|
ENV VAULT_VERSION "v2.11.0"
|
||||||
|
|
||||||
ENV URL "https://github.com/dani-garcia/bw_web_builds/releases/download/$VAULT_VERSION/bw_web_$VAULT_VERSION.tar.gz"
|
ENV URL "https://github.com/dani-garcia/bw_web_builds/releases/download/$VAULT_VERSION/bw_web_$VAULT_VERSION.tar.gz"
|
||||||
|
|
||||||
RUN apk add --update-cache --upgrade \
|
RUN apk add --no-cache --upgrade \
|
||||||
curl \
|
curl \
|
||||||
tar
|
tar
|
||||||
|
|
||||||
RUN mkdir /web-vault
|
RUN mkdir /web-vault
|
||||||
WORKDIR /web-vault
|
WORKDIR /web-vault
|
||||||
|
|
||||||
|
SHELL ["/bin/ash", "-eo", "pipefail", "-c"]
|
||||||
|
|
||||||
RUN curl -L $URL | tar xz
|
RUN curl -L $URL | tar xz
|
||||||
RUN ls
|
RUN ls
|
||||||
|
|
||||||
########################## BUILD IMAGE ##########################
|
########################## BUILD IMAGE ##########################
|
||||||
# We need to use the Rust build image, because
|
# We need to use the Rust build image, because
|
||||||
# we need the Rust compiler and Cargo tooling
|
# we need the Rust compiler and Cargo tooling
|
||||||
FROM rust as build
|
FROM rust:1.36 as build
|
||||||
|
|
||||||
|
# set mysql backend
|
||||||
|
ARG DB=mysql
|
||||||
|
|
||||||
RUN apt-get update \
|
RUN apt-get update \
|
||||||
&& apt-get install -y \
|
&& apt-get install -y \
|
||||||
|
--no-install-recommends \
|
||||||
gcc-arm-linux-gnueabihf \
|
gcc-arm-linux-gnueabihf \
|
||||||
&& mkdir -p ~/.cargo \
|
&& mkdir -p ~/.cargo \
|
||||||
&& echo '[target.armv7-unknown-linux-gnueabihf]' >> ~/.cargo/config \
|
&& echo '[target.armv7-unknown-linux-gnueabihf]' >> ~/.cargo/config \
|
||||||
@@ -41,8 +47,11 @@ RUN sed 's/^deb/deb-src/' /etc/apt/sources.list > \
|
|||||||
&& dpkg --add-architecture armhf \
|
&& dpkg --add-architecture armhf \
|
||||||
&& apt-get update \
|
&& apt-get update \
|
||||||
&& apt-get install -y \
|
&& apt-get install -y \
|
||||||
|
--no-install-recommends \
|
||||||
libssl-dev:armhf \
|
libssl-dev:armhf \
|
||||||
libc6-dev:armhf
|
libc6-dev:armhf \
|
||||||
|
libmariadb-dev:armhf
|
||||||
|
|
||||||
|
|
||||||
ENV CC_armv7_unknown_linux_gnueabihf="/usr/bin/arm-linux-gnueabihf-gcc"
|
ENV CC_armv7_unknown_linux_gnueabihf="/usr/bin/arm-linux-gnueabihf-gcc"
|
||||||
ENV CROSS_COMPILE="1"
|
ENV CROSS_COMPILE="1"
|
||||||
@@ -55,7 +64,7 @@ COPY . .
|
|||||||
|
|
||||||
# Build
|
# Build
|
||||||
RUN rustup target add armv7-unknown-linux-gnueabihf
|
RUN rustup target add armv7-unknown-linux-gnueabihf
|
||||||
RUN cargo build --release --target=armv7-unknown-linux-gnueabihf -v
|
RUN cargo build --features ${DB} --release --target=armv7-unknown-linux-gnueabihf -v
|
||||||
|
|
||||||
######################## RUNTIME IMAGE ########################
|
######################## RUNTIME IMAGE ########################
|
||||||
# Create a new stage with a minimal image
|
# Create a new stage with a minimal image
|
||||||
@@ -69,11 +78,12 @@ ENV ROCKET_WORKERS=10
|
|||||||
RUN [ "cross-build-start" ]
|
RUN [ "cross-build-start" ]
|
||||||
|
|
||||||
# Install needed libraries
|
# Install needed libraries
|
||||||
RUN apt-get update && apt-get install -y\
|
RUN apt-get update && apt-get install -y \
|
||||||
openssl\
|
--no-install-recommends \
|
||||||
ca-certificates\
|
openssl \
|
||||||
--no-install-recommends\
|
ca-certificates \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
libmariadbclient-dev \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
RUN mkdir /data
|
RUN mkdir /data
|
||||||
|
|
||||||
@@ -89,4 +99,4 @@ COPY --from=vault /web-vault ./web-vault
|
|||||||
COPY --from=build /app/target/armv7-unknown-linux-gnueabihf/release/bitwarden_rs .
|
COPY --from=build /app/target/armv7-unknown-linux-gnueabihf/release/bitwarden_rs .
|
||||||
|
|
||||||
# Configures the startup!
|
# Configures the startup!
|
||||||
CMD ./bitwarden_rs
|
CMD ["./bitwarden_rs"]
|
101
docker/armv7/sqlite/Dockerfile
Normal file
101
docker/armv7/sqlite/Dockerfile
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
# Using multistage build:
|
||||||
|
# https://docs.docker.com/develop/develop-images/multistage-build/
|
||||||
|
# https://whitfin.io/speeding-up-rust-docker-builds/
|
||||||
|
####################### VAULT BUILD IMAGE #######################
|
||||||
|
FROM alpine:3.10 as vault
|
||||||
|
|
||||||
|
ENV VAULT_VERSION "v2.11.0"
|
||||||
|
|
||||||
|
ENV URL "https://github.com/dani-garcia/bw_web_builds/releases/download/$VAULT_VERSION/bw_web_$VAULT_VERSION.tar.gz"
|
||||||
|
|
||||||
|
RUN apk add --no-cache --upgrade \
|
||||||
|
curl \
|
||||||
|
tar
|
||||||
|
|
||||||
|
RUN mkdir /web-vault
|
||||||
|
WORKDIR /web-vault
|
||||||
|
|
||||||
|
SHELL ["/bin/ash", "-eo", "pipefail", "-c"]
|
||||||
|
|
||||||
|
RUN curl -L $URL | tar xz
|
||||||
|
RUN ls
|
||||||
|
|
||||||
|
########################## BUILD IMAGE ##########################
|
||||||
|
# We need to use the Rust build image, because
|
||||||
|
# we need the Rust compiler and Cargo tooling
|
||||||
|
FROM rust:1.36 as build
|
||||||
|
|
||||||
|
# set sqlite as default for DB ARG for backward comaptibility
|
||||||
|
ARG DB=sqlite
|
||||||
|
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install -y \
|
||||||
|
--no-install-recommends \
|
||||||
|
gcc-arm-linux-gnueabihf \
|
||||||
|
&& mkdir -p ~/.cargo \
|
||||||
|
&& echo '[target.armv7-unknown-linux-gnueabihf]' >> ~/.cargo/config \
|
||||||
|
&& echo 'linker = "arm-linux-gnueabihf-gcc"' >> ~/.cargo/config
|
||||||
|
|
||||||
|
ENV CARGO_HOME "/root/.cargo"
|
||||||
|
ENV USER "root"
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Prepare openssl armhf libs
|
||||||
|
RUN sed 's/^deb/deb-src/' /etc/apt/sources.list > \
|
||||||
|
/etc/apt/sources.list.d/deb-src.list \
|
||||||
|
&& dpkg --add-architecture armhf \
|
||||||
|
&& apt-get update \
|
||||||
|
&& apt-get install -y \
|
||||||
|
--no-install-recommends \
|
||||||
|
libssl-dev:armhf \
|
||||||
|
libc6-dev:armhf \
|
||||||
|
libmariadb-dev:armhf
|
||||||
|
|
||||||
|
ENV CC_armv7_unknown_linux_gnueabihf="/usr/bin/arm-linux-gnueabihf-gcc"
|
||||||
|
ENV CROSS_COMPILE="1"
|
||||||
|
ENV OPENSSL_INCLUDE_DIR="/usr/include/arm-linux-gnueabihf"
|
||||||
|
ENV OPENSSL_LIB_DIR="/usr/lib/arm-linux-gnueabihf"
|
||||||
|
|
||||||
|
# Copies the complete project
|
||||||
|
# To avoid copying unneeded files, use .dockerignore
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Build
|
||||||
|
RUN rustup target add armv7-unknown-linux-gnueabihf
|
||||||
|
RUN cargo build --features ${DB} --release --target=armv7-unknown-linux-gnueabihf -v
|
||||||
|
|
||||||
|
######################## RUNTIME IMAGE ########################
|
||||||
|
# Create a new stage with a minimal image
|
||||||
|
# because we already have a binary built
|
||||||
|
FROM balenalib/armv7hf-debian:stretch
|
||||||
|
|
||||||
|
ENV ROCKET_ENV "staging"
|
||||||
|
ENV ROCKET_PORT=80
|
||||||
|
ENV ROCKET_WORKERS=10
|
||||||
|
|
||||||
|
RUN [ "cross-build-start" ]
|
||||||
|
|
||||||
|
# Install needed libraries
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
|
--no-install-recommends \
|
||||||
|
openssl \
|
||||||
|
ca-certificates \
|
||||||
|
libmariadbclient-dev \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
RUN mkdir /data
|
||||||
|
|
||||||
|
RUN [ "cross-build-end" ]
|
||||||
|
|
||||||
|
VOLUME /data
|
||||||
|
EXPOSE 80
|
||||||
|
|
||||||
|
# Copies the files from the context (Rocket.toml file and web-vault)
|
||||||
|
# and the binary from the "build" stage to the current stage
|
||||||
|
COPY Rocket.toml .
|
||||||
|
COPY --from=vault /web-vault ./web-vault
|
||||||
|
COPY --from=build /app/target/armv7-unknown-linux-gnueabihf/release/bitwarden_rs .
|
||||||
|
|
||||||
|
# Configures the startup!
|
||||||
|
CMD ["./bitwarden_rs"]
|
62
migrations/mysql/2018-01-14-171611_create_tables/up.sql
Normal file
62
migrations/mysql/2018-01-14-171611_create_tables/up.sql
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
CREATE TABLE users (
|
||||||
|
uuid CHAR(36) NOT NULL PRIMARY KEY,
|
||||||
|
created_at DATETIME NOT NULL,
|
||||||
|
updated_at DATETIME NOT NULL,
|
||||||
|
email VARCHAR(255) NOT NULL UNIQUE,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
password_hash BLOB NOT NULL,
|
||||||
|
salt BLOB NOT NULL,
|
||||||
|
password_iterations INTEGER NOT NULL,
|
||||||
|
password_hint TEXT,
|
||||||
|
`key` TEXT NOT NULL,
|
||||||
|
private_key TEXT,
|
||||||
|
public_key TEXT,
|
||||||
|
totp_secret TEXT,
|
||||||
|
totp_recover TEXT,
|
||||||
|
security_stamp TEXT NOT NULL,
|
||||||
|
equivalent_domains TEXT NOT NULL,
|
||||||
|
excluded_globals TEXT NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE devices (
|
||||||
|
uuid CHAR(36) NOT NULL PRIMARY KEY,
|
||||||
|
created_at DATETIME NOT NULL,
|
||||||
|
updated_at DATETIME NOT NULL,
|
||||||
|
user_uuid CHAR(36) NOT NULL REFERENCES users (uuid),
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
type INTEGER NOT NULL,
|
||||||
|
push_token TEXT,
|
||||||
|
refresh_token TEXT NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE ciphers (
|
||||||
|
uuid CHAR(36) NOT NULL PRIMARY KEY,
|
||||||
|
created_at DATETIME NOT NULL,
|
||||||
|
updated_at DATETIME NOT NULL,
|
||||||
|
user_uuid CHAR(36) NOT NULL REFERENCES users (uuid),
|
||||||
|
folder_uuid CHAR(36) REFERENCES folders (uuid),
|
||||||
|
organization_uuid CHAR(36),
|
||||||
|
type INTEGER NOT NULL,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
notes TEXT,
|
||||||
|
fields TEXT,
|
||||||
|
data TEXT NOT NULL,
|
||||||
|
favorite BOOLEAN NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE attachments (
|
||||||
|
id CHAR(36) NOT NULL PRIMARY KEY,
|
||||||
|
cipher_uuid CHAR(36) NOT NULL REFERENCES ciphers (uuid),
|
||||||
|
file_name TEXT NOT NULL,
|
||||||
|
file_size INTEGER NOT NULL
|
||||||
|
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE folders (
|
||||||
|
uuid CHAR(36) NOT NULL PRIMARY KEY,
|
||||||
|
created_at DATETIME NOT NULL,
|
||||||
|
updated_at DATETIME NOT NULL,
|
||||||
|
user_uuid CHAR(36) NOT NULL REFERENCES users (uuid),
|
||||||
|
name TEXT NOT NULL
|
||||||
|
);
|
||||||
|
|
@@ -0,0 +1,30 @@
|
|||||||
|
CREATE TABLE collections (
|
||||||
|
uuid VARCHAR(40) NOT NULL PRIMARY KEY,
|
||||||
|
org_uuid VARCHAR(40) NOT NULL REFERENCES organizations (uuid),
|
||||||
|
name TEXT NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE organizations (
|
||||||
|
uuid VARCHAR(40) NOT NULL PRIMARY KEY,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
billing_email TEXT NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE users_collections (
|
||||||
|
user_uuid CHAR(36) NOT NULL REFERENCES users (uuid),
|
||||||
|
collection_uuid CHAR(36) NOT NULL REFERENCES collections (uuid),
|
||||||
|
PRIMARY KEY (user_uuid, collection_uuid)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE users_organizations (
|
||||||
|
uuid CHAR(36) NOT NULL PRIMARY KEY,
|
||||||
|
user_uuid CHAR(36) NOT NULL REFERENCES users (uuid),
|
||||||
|
org_uuid CHAR(36) NOT NULL REFERENCES organizations (uuid),
|
||||||
|
|
||||||
|
access_all BOOLEAN NOT NULL,
|
||||||
|
`key` TEXT NOT NULL,
|
||||||
|
status INTEGER NOT NULL,
|
||||||
|
type INTEGER NOT NULL,
|
||||||
|
|
||||||
|
UNIQUE (user_uuid, org_uuid)
|
||||||
|
);
|
@@ -0,0 +1,34 @@
|
|||||||
|
ALTER TABLE ciphers RENAME TO oldCiphers;
|
||||||
|
|
||||||
|
CREATE TABLE ciphers (
|
||||||
|
uuid CHAR(36) NOT NULL PRIMARY KEY,
|
||||||
|
created_at DATETIME NOT NULL,
|
||||||
|
updated_at DATETIME NOT NULL,
|
||||||
|
user_uuid CHAR(36) REFERENCES users (uuid), -- Make this optional
|
||||||
|
organization_uuid CHAR(36) REFERENCES organizations (uuid), -- Add reference to orgs table
|
||||||
|
-- Remove folder_uuid
|
||||||
|
type INTEGER NOT NULL,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
notes TEXT,
|
||||||
|
fields TEXT,
|
||||||
|
data TEXT NOT NULL,
|
||||||
|
favorite BOOLEAN NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE folders_ciphers (
|
||||||
|
cipher_uuid CHAR(36) NOT NULL REFERENCES ciphers (uuid),
|
||||||
|
folder_uuid CHAR(36) NOT NULL REFERENCES folders (uuid),
|
||||||
|
|
||||||
|
PRIMARY KEY (cipher_uuid, folder_uuid)
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO ciphers (uuid, created_at, updated_at, user_uuid, organization_uuid, type, name, notes, fields, data, favorite)
|
||||||
|
SELECT uuid, created_at, updated_at, user_uuid, organization_uuid, type, name, notes, fields, data, favorite FROM oldCiphers;
|
||||||
|
|
||||||
|
INSERT INTO folders_ciphers (cipher_uuid, folder_uuid)
|
||||||
|
SELECT uuid, folder_uuid FROM oldCiphers WHERE folder_uuid IS NOT NULL;
|
||||||
|
|
||||||
|
|
||||||
|
DROP TABLE oldCiphers;
|
||||||
|
|
||||||
|
ALTER TABLE users_collections ADD COLUMN read_only BOOLEAN NOT NULL DEFAULT 0; -- False
|
@@ -0,0 +1,5 @@
|
|||||||
|
CREATE TABLE ciphers_collections (
|
||||||
|
cipher_uuid CHAR(36) NOT NULL REFERENCES ciphers (uuid),
|
||||||
|
collection_uuid CHAR(36) NOT NULL REFERENCES collections (uuid),
|
||||||
|
PRIMARY KEY (cipher_uuid, collection_uuid)
|
||||||
|
);
|
@@ -0,0 +1,14 @@
|
|||||||
|
ALTER TABLE attachments RENAME TO oldAttachments;
|
||||||
|
|
||||||
|
CREATE TABLE attachments (
|
||||||
|
id CHAR(36) NOT NULL PRIMARY KEY,
|
||||||
|
cipher_uuid CHAR(36) NOT NULL REFERENCES ciphers (uuid),
|
||||||
|
file_name TEXT NOT NULL,
|
||||||
|
file_size INTEGER NOT NULL
|
||||||
|
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO attachments (id, cipher_uuid, file_name, file_size)
|
||||||
|
SELECT id, cipher_uuid, file_name, file_size FROM oldAttachments;
|
||||||
|
|
||||||
|
DROP TABLE oldAttachments;
|
@@ -0,0 +1,15 @@
|
|||||||
|
CREATE TABLE twofactor (
|
||||||
|
uuid CHAR(36) NOT NULL PRIMARY KEY,
|
||||||
|
user_uuid CHAR(36) NOT NULL REFERENCES users (uuid),
|
||||||
|
type INTEGER NOT NULL,
|
||||||
|
enabled BOOLEAN NOT NULL,
|
||||||
|
data TEXT NOT NULL,
|
||||||
|
|
||||||
|
UNIQUE (user_uuid, type)
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
INSERT INTO twofactor (uuid, user_uuid, type, enabled, data)
|
||||||
|
SELECT UUID(), uuid, 0, 1, u.totp_secret FROM users u where u.totp_secret IS NOT NULL;
|
||||||
|
|
||||||
|
UPDATE users SET totp_secret = NULL; -- Instead of recreating the table, just leave the columns empty
|
3
migrations/mysql/2018-09-10-111213_add_invites/up.sql
Normal file
3
migrations/mysql/2018-09-10-111213_add_invites/up.sql
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
CREATE TABLE invitations (
|
||||||
|
email VARCHAR(255) NOT NULL PRIMARY KEY
|
||||||
|
);
|
@@ -0,0 +1,3 @@
|
|||||||
|
ALTER TABLE attachments
|
||||||
|
ADD COLUMN
|
||||||
|
`key` TEXT;
|
@@ -0,0 +1,7 @@
|
|||||||
|
ALTER TABLE attachments CHANGE COLUMN akey `key` TEXT;
|
||||||
|
ALTER TABLE ciphers CHANGE COLUMN atype type INTEGER NOT NULL;
|
||||||
|
ALTER TABLE devices CHANGE COLUMN atype type INTEGER NOT NULL;
|
||||||
|
ALTER TABLE twofactor CHANGE COLUMN atype type INTEGER NOT NULL;
|
||||||
|
ALTER TABLE users CHANGE COLUMN akey `key` TEXT;
|
||||||
|
ALTER TABLE users_organizations CHANGE COLUMN akey `key` TEXT;
|
||||||
|
ALTER TABLE users_organizations CHANGE COLUMN atype type INTEGER NOT NULL;
|
@@ -0,0 +1,7 @@
|
|||||||
|
ALTER TABLE attachments CHANGE COLUMN `key` akey TEXT;
|
||||||
|
ALTER TABLE ciphers CHANGE COLUMN type atype INTEGER NOT NULL;
|
||||||
|
ALTER TABLE devices CHANGE COLUMN type atype INTEGER NOT NULL;
|
||||||
|
ALTER TABLE twofactor CHANGE COLUMN type atype INTEGER NOT NULL;
|
||||||
|
ALTER TABLE users CHANGE COLUMN `key` akey TEXT;
|
||||||
|
ALTER TABLE users_organizations CHANGE COLUMN `key` akey TEXT;
|
||||||
|
ALTER TABLE users_organizations CHANGE COLUMN type atype INTEGER NOT NULL;
|
@@ -0,0 +1,9 @@
|
|||||||
|
DROP TABLE users;
|
||||||
|
|
||||||
|
DROP TABLE devices;
|
||||||
|
|
||||||
|
DROP TABLE ciphers;
|
||||||
|
|
||||||
|
DROP TABLE attachments;
|
||||||
|
|
||||||
|
DROP TABLE folders;
|
@@ -0,0 +1,8 @@
|
|||||||
|
DROP TABLE collections;
|
||||||
|
|
||||||
|
DROP TABLE organizations;
|
||||||
|
|
||||||
|
|
||||||
|
DROP TABLE users_collections;
|
||||||
|
|
||||||
|
DROP TABLE users_organizations;
|
@@ -0,0 +1 @@
|
|||||||
|
DROP TABLE ciphers_collections;
|
@@ -0,0 +1 @@
|
|||||||
|
-- This file should undo anything in `up.sql`
|
@@ -0,0 +1,3 @@
|
|||||||
|
ALTER TABLE devices
|
||||||
|
ADD COLUMN
|
||||||
|
twofactor_remember TEXT;
|
@@ -0,0 +1,8 @@
|
|||||||
|
UPDATE users
|
||||||
|
SET totp_secret = (
|
||||||
|
SELECT twofactor.data FROM twofactor
|
||||||
|
WHERE twofactor.type = 0
|
||||||
|
AND twofactor.user_uuid = users.uuid
|
||||||
|
);
|
||||||
|
|
||||||
|
DROP TABLE twofactor;
|
@@ -0,0 +1,3 @@
|
|||||||
|
ALTER TABLE ciphers
|
||||||
|
ADD COLUMN
|
||||||
|
password_history TEXT;
|
1
migrations/sqlite/2018-09-10-111213_add_invites/down.sql
Normal file
1
migrations/sqlite/2018-09-10-111213_add_invites/down.sql
Normal file
@@ -0,0 +1 @@
|
|||||||
|
DROP TABLE invitations;
|
@@ -0,0 +1,7 @@
|
|||||||
|
ALTER TABLE users
|
||||||
|
ADD COLUMN
|
||||||
|
client_kdf_type INTEGER NOT NULL DEFAULT 0; -- PBKDF2
|
||||||
|
|
||||||
|
ALTER TABLE users
|
||||||
|
ADD COLUMN
|
||||||
|
client_kdf_iter INTEGER NOT NULL DEFAULT 5000;
|
@@ -0,0 +1,7 @@
|
|||||||
|
ALTER TABLE attachments RENAME COLUMN akey TO key;
|
||||||
|
ALTER TABLE ciphers RENAME COLUMN atype TO type;
|
||||||
|
ALTER TABLE devices RENAME COLUMN atype TO type;
|
||||||
|
ALTER TABLE twofactor RENAME COLUMN atype TO type;
|
||||||
|
ALTER TABLE users RENAME COLUMN akey TO key;
|
||||||
|
ALTER TABLE users_organizations RENAME COLUMN akey TO key;
|
||||||
|
ALTER TABLE users_organizations RENAME COLUMN atype TO type;
|
@@ -0,0 +1,7 @@
|
|||||||
|
ALTER TABLE attachments RENAME COLUMN key TO akey;
|
||||||
|
ALTER TABLE ciphers RENAME COLUMN type TO atype;
|
||||||
|
ALTER TABLE devices RENAME COLUMN type TO atype;
|
||||||
|
ALTER TABLE twofactor RENAME COLUMN type TO atype;
|
||||||
|
ALTER TABLE users RENAME COLUMN key TO akey;
|
||||||
|
ALTER TABLE users_organizations RENAME COLUMN key TO akey;
|
||||||
|
ALTER TABLE users_organizations RENAME COLUMN type TO atype;
|
@@ -1 +1 @@
|
|||||||
nightly-2019-05-11
|
nightly-2019-08-18
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
use rocket::http::{Cookie, Cookies, SameSite};
|
use rocket::http::{Cookie, Cookies, SameSite};
|
||||||
use rocket::request::{self, FlashMessage, Form, FromRequest, Request};
|
use rocket::request::{self, FlashMessage, Form, FromRequest, Request};
|
||||||
@@ -9,7 +10,7 @@ use rocket_contrib::json::Json;
|
|||||||
use crate::api::{ApiResult, EmptyResult, JsonResult};
|
use crate::api::{ApiResult, EmptyResult, JsonResult};
|
||||||
use crate::auth::{decode_admin, encode_jwt, generate_admin_claims, ClientIp};
|
use crate::auth::{decode_admin, encode_jwt, generate_admin_claims, ClientIp};
|
||||||
use crate::config::ConfigBuilder;
|
use crate::config::ConfigBuilder;
|
||||||
use crate::db::{models::*, DbConn};
|
use crate::db::{backup_database, models::*, DbConn};
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
use crate::mail;
|
use crate::mail;
|
||||||
use crate::CONFIG;
|
use crate::CONFIG;
|
||||||
@@ -27,12 +28,18 @@ pub fn routes() -> Vec<Route> {
|
|||||||
invite_user,
|
invite_user,
|
||||||
delete_user,
|
delete_user,
|
||||||
deauth_user,
|
deauth_user,
|
||||||
|
remove_2fa,
|
||||||
update_revision_users,
|
update_revision_users,
|
||||||
post_config,
|
post_config,
|
||||||
delete_config,
|
delete_config,
|
||||||
|
backup_db,
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref CAN_BACKUP: bool = cfg!(feature = "sqlite") && Command::new("sqlite").arg("-version").status().is_ok();
|
||||||
|
}
|
||||||
|
|
||||||
#[get("/")]
|
#[get("/")]
|
||||||
fn admin_disabled() -> &'static str {
|
fn admin_disabled() -> &'static str {
|
||||||
"The admin panel is disabled, please configure the 'ADMIN_TOKEN' variable to enable it"
|
"The admin panel is disabled, please configure the 'ADMIN_TOKEN' variable to enable it"
|
||||||
@@ -101,6 +108,7 @@ struct AdminTemplateData {
|
|||||||
version: Option<&'static str>,
|
version: Option<&'static str>,
|
||||||
users: Vec<Value>,
|
users: Vec<Value>,
|
||||||
config: Value,
|
config: Value,
|
||||||
|
can_backup: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AdminTemplateData {
|
impl AdminTemplateData {
|
||||||
@@ -110,6 +118,7 @@ impl AdminTemplateData {
|
|||||||
version: VERSION,
|
version: VERSION,
|
||||||
users,
|
users,
|
||||||
config: CONFIG.prepare_json(),
|
config: CONFIG.prepare_json(),
|
||||||
|
can_backup: *CAN_BACKUP,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,6 +197,18 @@ fn deauth_user(uuid: String, _token: AdminToken, conn: DbConn) -> EmptyResult {
|
|||||||
user.save(&conn)
|
user.save(&conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[post("/users/<uuid>/remove-2fa")]
|
||||||
|
fn remove_2fa(uuid: String, _token: AdminToken, conn: DbConn) -> EmptyResult {
|
||||||
|
let mut user = match User::find_by_uuid(&uuid, &conn) {
|
||||||
|
Some(user) => user,
|
||||||
|
None => err!("User doesn't exist"),
|
||||||
|
};
|
||||||
|
|
||||||
|
TwoFactor::delete_all_by_user(&user.uuid, &conn)?;
|
||||||
|
user.totp_recover = None;
|
||||||
|
user.save(&conn)
|
||||||
|
}
|
||||||
|
|
||||||
#[post("/users/update_revision")]
|
#[post("/users/update_revision")]
|
||||||
fn update_revision_users(_token: AdminToken, conn: DbConn) -> EmptyResult {
|
fn update_revision_users(_token: AdminToken, conn: DbConn) -> EmptyResult {
|
||||||
User::update_all_revisions(&conn)
|
User::update_all_revisions(&conn)
|
||||||
@@ -204,6 +225,15 @@ fn delete_config(_token: AdminToken) -> EmptyResult {
|
|||||||
CONFIG.delete_user_config()
|
CONFIG.delete_user_config()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[post("/config/backup_db")]
|
||||||
|
fn backup_db(_token: AdminToken) -> EmptyResult {
|
||||||
|
if *CAN_BACKUP {
|
||||||
|
backup_database()
|
||||||
|
} else {
|
||||||
|
err!("Can't back up current DB (either it's not SQLite or the 'sqlite' binary is not present)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct AdminToken {}
|
pub struct AdminToken {}
|
||||||
|
|
||||||
impl<'a, 'r> FromRequest<'a, 'r> for AdminToken {
|
impl<'a, 'r> FromRequest<'a, 'r> for AdminToken {
|
||||||
|
@@ -106,7 +106,7 @@ fn register(data: JsonUpcase<RegisterData>, conn: DbConn) -> EmptyResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
user.set_password(&data.MasterPasswordHash);
|
user.set_password(&data.MasterPasswordHash);
|
||||||
user.key = data.Key;
|
user.akey = data.Key;
|
||||||
|
|
||||||
// Add extra fields if present
|
// Add extra fields if present
|
||||||
if let Some(name) = data.Name {
|
if let Some(name) = data.Name {
|
||||||
@@ -204,7 +204,7 @@ fn post_password(data: JsonUpcase<ChangePassData>, headers: Headers, conn: DbCon
|
|||||||
}
|
}
|
||||||
|
|
||||||
user.set_password(&data.NewMasterPasswordHash);
|
user.set_password(&data.NewMasterPasswordHash);
|
||||||
user.key = data.Key;
|
user.akey = data.Key;
|
||||||
user.save(&conn)
|
user.save(&conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -231,7 +231,7 @@ fn post_kdf(data: JsonUpcase<ChangeKdfData>, headers: Headers, conn: DbConn) ->
|
|||||||
user.client_kdf_iter = data.KdfIterations;
|
user.client_kdf_iter = data.KdfIterations;
|
||||||
user.client_kdf_type = data.Kdf;
|
user.client_kdf_type = data.Kdf;
|
||||||
user.set_password(&data.NewMasterPasswordHash);
|
user.set_password(&data.NewMasterPasswordHash);
|
||||||
user.key = data.Key;
|
user.akey = data.Key;
|
||||||
user.save(&conn)
|
user.save(&conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -306,7 +306,7 @@ fn post_rotatekey(data: JsonUpcase<KeyData>, headers: Headers, conn: DbConn, nt:
|
|||||||
// Update user data
|
// Update user data
|
||||||
let mut user = headers.user;
|
let mut user = headers.user;
|
||||||
|
|
||||||
user.key = data.Key;
|
user.akey = data.Key;
|
||||||
user.private_key = Some(data.PrivateKey);
|
user.private_key = Some(data.PrivateKey);
|
||||||
user.reset_security_stamp();
|
user.reset_security_stamp();
|
||||||
|
|
||||||
@@ -377,7 +377,7 @@ fn post_email(data: JsonUpcase<ChangeEmailData>, headers: Headers, conn: DbConn)
|
|||||||
user.email = data.NewEmail;
|
user.email = data.NewEmail;
|
||||||
|
|
||||||
user.set_password(&data.NewMasterPasswordHash);
|
user.set_password(&data.NewMasterPasswordHash);
|
||||||
user.key = data.Key;
|
user.akey = data.Key;
|
||||||
|
|
||||||
user.save(&conn)
|
user.save(&conn)
|
||||||
}
|
}
|
||||||
|
@@ -267,7 +267,7 @@ pub fn update_cipher_from_data(
|
|||||||
err!("Attachment is not owned by the cipher")
|
err!("Attachment is not owned by the cipher")
|
||||||
}
|
}
|
||||||
|
|
||||||
saved_att.key = Some(attachment.Key);
|
saved_att.akey = Some(attachment.Key);
|
||||||
saved_att.file_name = attachment.FileName;
|
saved_att.file_name = attachment.FileName;
|
||||||
|
|
||||||
saved_att.save(&conn)?;
|
saved_att.save(&conn)?;
|
||||||
@@ -691,7 +691,7 @@ fn post_attachment(
|
|||||||
};
|
};
|
||||||
|
|
||||||
let mut attachment = Attachment::new(file_name, cipher.uuid.clone(), name, size);
|
let mut attachment = Attachment::new(file_name, cipher.uuid.clone(), name, size);
|
||||||
attachment.key = attachment_key.clone();
|
attachment.akey = attachment_key.clone();
|
||||||
attachment.save(&conn).expect("Error saving attachment");
|
attachment.save(&conn).expect("Error saving attachment");
|
||||||
}
|
}
|
||||||
_ => error!("Invalid multipart name"),
|
_ => error!("Invalid multipart name"),
|
||||||
@@ -899,7 +899,7 @@ fn delete_all(
|
|||||||
match UserOrganization::find_by_user_and_org(&user.uuid, &org_data.org_id, &conn) {
|
match UserOrganization::find_by_user_and_org(&user.uuid, &org_data.org_id, &conn) {
|
||||||
None => err!("You don't have permission to purge the organization vault"),
|
None => err!("You don't have permission to purge the organization vault"),
|
||||||
Some(user_org) => {
|
Some(user_org) => {
|
||||||
if user_org.type_ == UserOrgType::Owner {
|
if user_org.atype == UserOrgType::Owner {
|
||||||
Cipher::delete_all_by_organization(&org_data.org_id, &conn)?;
|
Cipher::delete_all_by_organization(&org_data.org_id, &conn)?;
|
||||||
Collection::delete_all_by_organization(&org_data.org_id, &conn)?;
|
Collection::delete_all_by_organization(&org_data.org_id, &conn)?;
|
||||||
nt.send_user_update(UpdateType::Vault, &user);
|
nt.send_user_update(UpdateType::Vault, &user);
|
||||||
|
@@ -63,7 +63,7 @@ fn put_device_token(uuid: String, data: JsonUpcase<Value>, headers: Headers) ->
|
|||||||
Ok(Json(json!({
|
Ok(Json(json!({
|
||||||
"Id": headers.device.uuid,
|
"Id": headers.device.uuid,
|
||||||
"Name": headers.device.name,
|
"Name": headers.device.name,
|
||||||
"Type": headers.device.type_,
|
"Type": headers.device.atype,
|
||||||
"Identifier": headers.device.uuid,
|
"Identifier": headers.device.uuid,
|
||||||
"CreationDate": crate::util::format_date(&headers.device.created_at),
|
"CreationDate": crate::util::format_date(&headers.device.created_at),
|
||||||
})))
|
})))
|
||||||
@@ -132,18 +132,33 @@ fn put_eq_domains(data: JsonUpcase<EquivDomainData>, headers: Headers, conn: DbC
|
|||||||
|
|
||||||
#[get("/hibp/breach?<username>")]
|
#[get("/hibp/breach?<username>")]
|
||||||
fn hibp_breach(username: String) -> JsonResult {
|
fn hibp_breach(username: String) -> JsonResult {
|
||||||
let url = format!("https://haveibeenpwned.com/api/v2/breachedaccount/{}", username);
|
|
||||||
let user_agent = "Bitwarden_RS";
|
let user_agent = "Bitwarden_RS";
|
||||||
|
let url = format!(
|
||||||
|
"https://haveibeenpwned.com/api/v3/breachedaccount/{}?truncateResponse=false&includeUnverified=false",
|
||||||
|
username
|
||||||
|
);
|
||||||
|
|
||||||
use reqwest::{header::USER_AGENT, Client};
|
use reqwest::{header::USER_AGENT, Client};
|
||||||
|
|
||||||
let res = Client::new().get(&url).header(USER_AGENT, user_agent).send()?;
|
if let Some(api_key) = crate::CONFIG.hibp_api_key() {
|
||||||
|
let res = Client::new()
|
||||||
|
.get(&url)
|
||||||
|
.header(USER_AGENT, user_agent)
|
||||||
|
.header("hibp-api-key", api_key)
|
||||||
|
.send()?;
|
||||||
|
|
||||||
// If we get a 404, return a 404, it means no breached accounts
|
// If we get a 404, return a 404, it means no breached accounts
|
||||||
if res.status() == 404 {
|
if res.status() == 404 {
|
||||||
return Err(Error::empty().with_code(404));
|
return Err(Error::empty().with_code(404));
|
||||||
|
}
|
||||||
|
|
||||||
|
let value: Value = res.error_for_status()?.json()?;
|
||||||
|
Ok(Json(value))
|
||||||
|
} else {
|
||||||
|
Ok(Json(json!([{
|
||||||
|
"title": "--- Error! ---",
|
||||||
|
"description": "HaveIBeenPwned API key not set! Go to https://haveibeenpwned.com/API/Key",
|
||||||
|
"logopath": "/bwrs_images/error-x.svg"
|
||||||
|
}])))
|
||||||
}
|
}
|
||||||
|
|
||||||
let value: Value = res.error_for_status()?.json()?;
|
|
||||||
Ok(Json(value))
|
|
||||||
}
|
}
|
||||||
|
@@ -80,9 +80,9 @@ fn create_organization(headers: Headers, data: JsonUpcase<OrgData>, conn: DbConn
|
|||||||
let mut user_org = UserOrganization::new(headers.user.uuid.clone(), org.uuid.clone());
|
let mut user_org = UserOrganization::new(headers.user.uuid.clone(), org.uuid.clone());
|
||||||
let collection = Collection::new(org.uuid.clone(), data.CollectionName);
|
let collection = Collection::new(org.uuid.clone(), data.CollectionName);
|
||||||
|
|
||||||
user_org.key = data.Key;
|
user_org.akey = data.Key;
|
||||||
user_org.access_all = true;
|
user_org.access_all = true;
|
||||||
user_org.type_ = UserOrgType::Owner as i32;
|
user_org.atype = UserOrgType::Owner as i32;
|
||||||
user_org.status = UserOrgStatus::Confirmed as i32;
|
user_org.status = UserOrgStatus::Confirmed as i32;
|
||||||
|
|
||||||
org.save(&conn)?;
|
org.save(&conn)?;
|
||||||
@@ -127,7 +127,7 @@ fn leave_organization(org_id: String, headers: Headers, conn: DbConn) -> EmptyRe
|
|||||||
match UserOrganization::find_by_user_and_org(&headers.user.uuid, &org_id, &conn) {
|
match UserOrganization::find_by_user_and_org(&headers.user.uuid, &org_id, &conn) {
|
||||||
None => err!("User not part of organization"),
|
None => err!("User not part of organization"),
|
||||||
Some(user_org) => {
|
Some(user_org) => {
|
||||||
if user_org.type_ == UserOrgType::Owner {
|
if user_org.atype == UserOrgType::Owner {
|
||||||
let num_owners =
|
let num_owners =
|
||||||
UserOrganization::find_by_org_and_type(&org_id, UserOrgType::Owner as i32, &conn).len();
|
UserOrganization::find_by_org_and_type(&org_id, UserOrgType::Owner as i32, &conn).len();
|
||||||
|
|
||||||
@@ -505,7 +505,7 @@ fn send_invite(org_id: String, data: JsonUpcase<InviteData>, headers: AdminHeade
|
|||||||
let mut new_user = UserOrganization::new(user.uuid.clone(), org_id.clone());
|
let mut new_user = UserOrganization::new(user.uuid.clone(), org_id.clone());
|
||||||
let access_all = data.AccessAll.unwrap_or(false);
|
let access_all = data.AccessAll.unwrap_or(false);
|
||||||
new_user.access_all = access_all;
|
new_user.access_all = access_all;
|
||||||
new_user.type_ = new_type;
|
new_user.atype = new_type;
|
||||||
new_user.status = user_org_status;
|
new_user.status = user_org_status;
|
||||||
|
|
||||||
// If no accessAll, add the collections received
|
// If no accessAll, add the collections received
|
||||||
@@ -657,7 +657,7 @@ fn confirm_invite(
|
|||||||
None => err!("The specified user isn't a member of the organization"),
|
None => err!("The specified user isn't a member of the organization"),
|
||||||
};
|
};
|
||||||
|
|
||||||
if user_to_confirm.type_ != UserOrgType::User && headers.org_user_type != UserOrgType::Owner {
|
if user_to_confirm.atype != UserOrgType::User && headers.org_user_type != UserOrgType::Owner {
|
||||||
err!("Only Owners can confirm Managers, Admins or Owners")
|
err!("Only Owners can confirm Managers, Admins or Owners")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -666,7 +666,7 @@ fn confirm_invite(
|
|||||||
}
|
}
|
||||||
|
|
||||||
user_to_confirm.status = UserOrgStatus::Confirmed as i32;
|
user_to_confirm.status = UserOrgStatus::Confirmed as i32;
|
||||||
user_to_confirm.key = match data["Key"].as_str() {
|
user_to_confirm.akey = match data["Key"].as_str() {
|
||||||
Some(key) => key.to_string(),
|
Some(key) => key.to_string(),
|
||||||
None => err!("Invalid key provided"),
|
None => err!("Invalid key provided"),
|
||||||
};
|
};
|
||||||
@@ -735,18 +735,18 @@ fn edit_user(
|
|||||||
None => err!("The specified user isn't member of the organization"),
|
None => err!("The specified user isn't member of the organization"),
|
||||||
};
|
};
|
||||||
|
|
||||||
if new_type != user_to_edit.type_
|
if new_type != user_to_edit.atype
|
||||||
&& (user_to_edit.type_ >= UserOrgType::Admin || new_type >= UserOrgType::Admin)
|
&& (user_to_edit.atype >= UserOrgType::Admin || new_type >= UserOrgType::Admin)
|
||||||
&& headers.org_user_type != UserOrgType::Owner
|
&& headers.org_user_type != UserOrgType::Owner
|
||||||
{
|
{
|
||||||
err!("Only Owners can grant and remove Admin or Owner privileges")
|
err!("Only Owners can grant and remove Admin or Owner privileges")
|
||||||
}
|
}
|
||||||
|
|
||||||
if user_to_edit.type_ == UserOrgType::Owner && headers.org_user_type != UserOrgType::Owner {
|
if user_to_edit.atype == UserOrgType::Owner && headers.org_user_type != UserOrgType::Owner {
|
||||||
err!("Only Owners can edit Owner users")
|
err!("Only Owners can edit Owner users")
|
||||||
}
|
}
|
||||||
|
|
||||||
if user_to_edit.type_ == UserOrgType::Owner && new_type != UserOrgType::Owner {
|
if user_to_edit.atype == UserOrgType::Owner && new_type != UserOrgType::Owner {
|
||||||
// Removing owner permmission, check that there are at least another owner
|
// Removing owner permmission, check that there are at least another owner
|
||||||
let num_owners = UserOrganization::find_by_org_and_type(&org_id, UserOrgType::Owner as i32, &conn).len();
|
let num_owners = UserOrganization::find_by_org_and_type(&org_id, UserOrgType::Owner as i32, &conn).len();
|
||||||
|
|
||||||
@@ -756,7 +756,7 @@ fn edit_user(
|
|||||||
}
|
}
|
||||||
|
|
||||||
user_to_edit.access_all = data.AccessAll;
|
user_to_edit.access_all = data.AccessAll;
|
||||||
user_to_edit.type_ = new_type as i32;
|
user_to_edit.atype = new_type as i32;
|
||||||
|
|
||||||
// Delete all the odd collections
|
// Delete all the odd collections
|
||||||
for c in CollectionUser::find_by_organization_and_user_uuid(&org_id, &user_to_edit.user_uuid, &conn) {
|
for c in CollectionUser::find_by_organization_and_user_uuid(&org_id, &user_to_edit.user_uuid, &conn) {
|
||||||
@@ -785,11 +785,11 @@ fn delete_user(org_id: String, org_user_id: String, headers: AdminHeaders, conn:
|
|||||||
None => err!("User to delete isn't member of the organization"),
|
None => err!("User to delete isn't member of the organization"),
|
||||||
};
|
};
|
||||||
|
|
||||||
if user_to_delete.type_ != UserOrgType::User && headers.org_user_type != UserOrgType::Owner {
|
if user_to_delete.atype != UserOrgType::User && headers.org_user_type != UserOrgType::Owner {
|
||||||
err!("Only Owners can delete Admins or Owners")
|
err!("Only Owners can delete Admins or Owners")
|
||||||
}
|
}
|
||||||
|
|
||||||
if user_to_delete.type_ == UserOrgType::Owner {
|
if user_to_delete.atype == UserOrgType::Owner {
|
||||||
// Removing owner, check that there are at least another owner
|
// Removing owner, check that there are at least another owner
|
||||||
let num_owners = UserOrganization::find_by_org_and_type(&org_id, UserOrgType::Owner as i32, &conn).len();
|
let num_owners = UserOrganization::find_by_org_and_type(&org_id, UserOrgType::Owner as i32, &conn).len();
|
||||||
|
|
||||||
@@ -842,7 +842,7 @@ fn post_org_import(
|
|||||||
None => err!("User is not part of the organization"),
|
None => err!("User is not part of the organization"),
|
||||||
};
|
};
|
||||||
|
|
||||||
if org_user.type_ < UserOrgType::Admin {
|
if org_user.atype < UserOrgType::Admin {
|
||||||
err!("Only admins or owners can import into an organization")
|
err!("Only admins or owners can import into an organization")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -95,9 +95,7 @@ fn recover(data: JsonUpcase<RecoverTwoFactor>, conn: DbConn) -> JsonResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Remove all twofactors from the user
|
// Remove all twofactors from the user
|
||||||
for twofactor in TwoFactor::find_by_user(&user.uuid, &conn) {
|
TwoFactor::delete_all_by_user(&user.uuid, &conn)?;
|
||||||
twofactor.delete(&conn)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove the recovery code, not needed without twofactors
|
// Remove the recovery code, not needed without twofactors
|
||||||
user.totp_recover = None;
|
user.totp_recover = None;
|
||||||
@@ -563,7 +561,7 @@ pub struct YubikeyMetadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
use yubico::config::Config;
|
use yubico::config::Config;
|
||||||
use yubico::Yubico;
|
use yubico::verify;
|
||||||
|
|
||||||
fn parse_yubikeys(data: &EnableYubikeyData) -> Vec<String> {
|
fn parse_yubikeys(data: &EnableYubikeyData) -> Vec<String> {
|
||||||
let data_keys = [&data.Key1, &data.Key2, &data.Key3, &data.Key4, &data.Key5];
|
let data_keys = [&data.Key1, &data.Key2, &data.Key3, &data.Key4, &data.Key5];
|
||||||
@@ -591,12 +589,11 @@ fn get_yubico_credentials() -> Result<(String, String), Error> {
|
|||||||
fn verify_yubikey_otp(otp: String) -> EmptyResult {
|
fn verify_yubikey_otp(otp: String) -> EmptyResult {
|
||||||
let (yubico_id, yubico_secret) = get_yubico_credentials()?;
|
let (yubico_id, yubico_secret) = get_yubico_credentials()?;
|
||||||
|
|
||||||
let yubico = Yubico::new();
|
|
||||||
let config = Config::default().set_client_id(yubico_id).set_key(yubico_secret);
|
let config = Config::default().set_client_id(yubico_id).set_key(yubico_secret);
|
||||||
|
|
||||||
match CONFIG.yubico_server() {
|
match CONFIG.yubico_server() {
|
||||||
Some(server) => yubico.verify(otp, config.set_api_hosts(vec![server])),
|
Some(server) => verify(otp, config.set_api_hosts(vec![server])),
|
||||||
None => yubico.verify(otp, config),
|
None => verify(otp, config),
|
||||||
}
|
}
|
||||||
.map_res("Failed to verify OTP")
|
.map_res("Failed to verify OTP")
|
||||||
.and(Ok(()))
|
.and(Ok(()))
|
||||||
|
@@ -27,6 +27,7 @@ const ALLOWED_CHARS: &str = "_-.";
|
|||||||
lazy_static! {
|
lazy_static! {
|
||||||
// Reuse the client between requests
|
// Reuse the client between requests
|
||||||
static ref CLIENT: Client = Client::builder()
|
static ref CLIENT: Client = Client::builder()
|
||||||
|
.use_sys_proxy()
|
||||||
.gzip(true)
|
.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())
|
||||||
@@ -204,9 +205,13 @@ fn get_icon_url(domain: &str) -> Result<(Vec<Icon>, String), Error> {
|
|||||||
let raw_cookies = content.headers().get_all("set-cookie");
|
let raw_cookies = content.headers().get_all("set-cookie");
|
||||||
cookie_str = raw_cookies
|
cookie_str = raw_cookies
|
||||||
.iter()
|
.iter()
|
||||||
.map(|raw_cookie| {
|
.filter_map(|raw_cookie| raw_cookie.to_str().ok())
|
||||||
let cookie = Cookie::parse(raw_cookie.to_str().unwrap_or_default()).unwrap();
|
.map(|cookie_str| {
|
||||||
format!("{}={}; ", cookie.name(), cookie.value())
|
if let Ok(cookie) = Cookie::parse(cookie_str) {
|
||||||
|
format!("{}={}; ", cookie.name(), cookie.value())
|
||||||
|
} else {
|
||||||
|
String::new()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.collect::<String>();
|
.collect::<String>();
|
||||||
|
|
||||||
|
@@ -15,6 +15,8 @@ use crate::api::{ApiResult, EmptyResult, JsonResult};
|
|||||||
|
|
||||||
use crate::auth::ClientIp;
|
use crate::auth::ClientIp;
|
||||||
|
|
||||||
|
use crate::mail;
|
||||||
|
|
||||||
use crate::CONFIG;
|
use crate::CONFIG;
|
||||||
|
|
||||||
pub fn routes() -> Vec<Route> {
|
pub fn routes() -> Vec<Route> {
|
||||||
@@ -68,7 +70,7 @@ fn _refresh_login(data: ConnectData, conn: DbConn) -> JsonResult {
|
|||||||
"expires_in": expires_in,
|
"expires_in": expires_in,
|
||||||
"token_type": "Bearer",
|
"token_type": "Bearer",
|
||||||
"refresh_token": device.refresh_token,
|
"refresh_token": device.refresh_token,
|
||||||
"Key": user.key,
|
"Key": user.akey,
|
||||||
"PrivateKey": user.private_key,
|
"PrivateKey": user.private_key,
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
@@ -99,27 +101,20 @@ fn _password_login(data: ConnectData, conn: DbConn, ip: ClientIp) -> JsonResult
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// On iOS, device_type sends "iOS", on others it sends a number
|
let (mut device, new_device) = get_device(&data, &conn, &user);
|
||||||
let device_type = util::try_parse_string(data.device_type.as_ref()).unwrap_or(0);
|
|
||||||
let device_id = data.device_identifier.clone().expect("No device id provided");
|
|
||||||
let device_name = data.device_name.clone().expect("No device name provided");
|
|
||||||
|
|
||||||
// Find device or create new
|
|
||||||
let mut device = match Device::find_by_uuid(&device_id, &conn) {
|
|
||||||
Some(device) => {
|
|
||||||
// Check if owned device, and recreate if not
|
|
||||||
if device.user_uuid != user.uuid {
|
|
||||||
info!("Device exists but is owned by another user. The old device will be discarded");
|
|
||||||
Device::new(device_id, user.uuid.clone(), device_name, device_type)
|
|
||||||
} else {
|
|
||||||
device
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => Device::new(device_id, user.uuid.clone(), device_name, device_type),
|
|
||||||
};
|
|
||||||
|
|
||||||
let twofactor_token = twofactor_auth(&user.uuid, &data, &mut device, &conn)?;
|
let twofactor_token = twofactor_auth(&user.uuid, &data, &mut device, &conn)?;
|
||||||
|
|
||||||
|
if CONFIG.mail_enabled() && new_device {
|
||||||
|
if let Err(e) = mail::send_new_device_logged_in(&user.email, &ip.ip.to_string(), &device.updated_at, &device.name) {
|
||||||
|
error!("Error sending new device email: {:#?}", e);
|
||||||
|
|
||||||
|
if CONFIG.require_device_email() {
|
||||||
|
err!("Could not send login notification email. Please contact your administrator.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Common
|
// Common
|
||||||
let user = User::find_by_uuid(&device.user_uuid, &conn).unwrap();
|
let user = User::find_by_uuid(&device.user_uuid, &conn).unwrap();
|
||||||
let orgs = UserOrganization::find_by_user(&user.uuid, &conn);
|
let orgs = UserOrganization::find_by_user(&user.uuid, &conn);
|
||||||
@@ -132,9 +127,8 @@ fn _password_login(data: ConnectData, conn: DbConn, ip: ClientIp) -> JsonResult
|
|||||||
"expires_in": expires_in,
|
"expires_in": expires_in,
|
||||||
"token_type": "Bearer",
|
"token_type": "Bearer",
|
||||||
"refresh_token": device.refresh_token,
|
"refresh_token": device.refresh_token,
|
||||||
"Key": user.key,
|
"Key": user.akey,
|
||||||
"PrivateKey": user.private_key,
|
"PrivateKey": user.private_key,
|
||||||
//"TwoFactorToken": "11122233333444555666777888999"
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if let Some(token) = twofactor_token {
|
if let Some(token) = twofactor_token {
|
||||||
@@ -145,6 +139,35 @@ fn _password_login(data: ConnectData, conn: DbConn, ip: ClientIp) -> JsonResult
|
|||||||
Ok(Json(result))
|
Ok(Json(result))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Retrieves an existing device or creates a new device from ConnectData and the User
|
||||||
|
fn get_device(data: &ConnectData, conn: &DbConn, user: &User) -> (Device, bool) {
|
||||||
|
// On iOS, device_type sends "iOS", on others it sends a number
|
||||||
|
let device_type = util::try_parse_string(data.device_type.as_ref()).unwrap_or(0);
|
||||||
|
let device_id = data.device_identifier.clone().expect("No device id provided");
|
||||||
|
let device_name = data.device_name.clone().expect("No device name provided");
|
||||||
|
|
||||||
|
let mut new_device = false;
|
||||||
|
// Find device or create new
|
||||||
|
let device = match Device::find_by_uuid(&device_id, &conn) {
|
||||||
|
Some(device) => {
|
||||||
|
// Check if owned device, and recreate if not
|
||||||
|
if device.user_uuid != user.uuid {
|
||||||
|
info!("Device exists but is owned by another user. The old device will be discarded");
|
||||||
|
new_device = true;
|
||||||
|
Device::new(device_id, user.uuid.clone(), device_name, device_type)
|
||||||
|
} else {
|
||||||
|
device
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
new_device = true;
|
||||||
|
Device::new(device_id, user.uuid.clone(), device_name, device_type)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
(device, new_device)
|
||||||
|
}
|
||||||
|
|
||||||
fn twofactor_auth(
|
fn twofactor_auth(
|
||||||
user_uuid: &str,
|
user_uuid: &str,
|
||||||
data: &ConnectData,
|
data: &ConnectData,
|
||||||
@@ -158,7 +181,7 @@ fn twofactor_auth(
|
|||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
let twofactor_ids: Vec<_> = twofactors.iter().map(|tf| tf.type_).collect();
|
let twofactor_ids: Vec<_> = twofactors.iter().map(|tf| tf.atype).collect();
|
||||||
let selected_id = data.two_factor_provider.unwrap_or(twofactor_ids[0]); // If we aren't given a two factor provider, asume the first one
|
let selected_id = data.two_factor_provider.unwrap_or(twofactor_ids[0]); // If we aren't given a two factor provider, asume the first one
|
||||||
|
|
||||||
let twofactor_code = match data.two_factor_token {
|
let twofactor_code = match data.two_factor_token {
|
||||||
@@ -166,7 +189,7 @@ fn twofactor_auth(
|
|||||||
None => err_json!(_json_err_twofactor(&twofactor_ids, user_uuid, conn)?),
|
None => err_json!(_json_err_twofactor(&twofactor_ids, user_uuid, conn)?),
|
||||||
};
|
};
|
||||||
|
|
||||||
let selected_twofactor = twofactors.into_iter().filter(|tf| tf.type_ == selected_id).nth(0);
|
let selected_twofactor = twofactors.into_iter().filter(|tf| tf.atype == selected_id).nth(0);
|
||||||
|
|
||||||
use crate::api::core::two_factor as _tf;
|
use crate::api::core::two_factor as _tf;
|
||||||
use crate::crypto::ct_eq;
|
use crate::crypto::ct_eq;
|
||||||
|
@@ -65,11 +65,11 @@ fn alive() -> Json<String> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[get("/bwrs_images/<filename>")]
|
#[get("/bwrs_images/<filename>")]
|
||||||
fn images(filename: String) -> Result<Content<Vec<u8>>, Error> {
|
fn images(filename: String) -> Result<Content<&'static [u8]>, Error> {
|
||||||
let image_type = ContentType::new("image", "png");
|
|
||||||
match filename.as_ref() {
|
match filename.as_ref() {
|
||||||
"mail-github.png" => Ok(Content(image_type , include_bytes!("../static/images/mail-github.png").to_vec())),
|
"mail-github.png" => Ok(Content(ContentType::PNG, include_bytes!("../static/images/mail-github.png"))),
|
||||||
"logo-gray.png" => Ok(Content(image_type, include_bytes!("../static/images/logo-gray.png").to_vec())),
|
"logo-gray.png" => Ok(Content(ContentType::PNG, include_bytes!("../static/images/logo-gray.png"))),
|
||||||
_ => err!("Image not found")
|
"error-x.svg" => Ok(Content(ContentType::SVG, include_bytes!("../static/images/error-x.svg"))),
|
||||||
|
_ => err!("Image not found"),
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -286,7 +286,7 @@ impl<'a, 'r> FromRequest<'a, 'r> for OrgHeaders {
|
|||||||
device: headers.device,
|
device: headers.device,
|
||||||
user,
|
user,
|
||||||
org_user_type: {
|
org_user_type: {
|
||||||
if let Some(org_usr_type) = UserOrgType::from_i32(org_user.type_) {
|
if let Some(org_usr_type) = UserOrgType::from_i32(org_user.atype) {
|
||||||
org_usr_type
|
org_usr_type
|
||||||
} else {
|
} else {
|
||||||
// This should only happen if the DB is corrupted
|
// This should only happen if the DB is corrupted
|
||||||
|
@@ -180,7 +180,7 @@ macro_rules! make_config {
|
|||||||
match $value {
|
match $value {
|
||||||
Some(v) => v,
|
Some(v) => v,
|
||||||
None => {
|
None => {
|
||||||
let f: &Fn(&ConfigItems) -> _ = &$default_fn;
|
let f: &dyn Fn(&ConfigItems) -> _ = &$default_fn;
|
||||||
f($config)
|
f($config)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -202,10 +202,9 @@ make_config! {
|
|||||||
folders {
|
folders {
|
||||||
/// Data folder |> Main data folder
|
/// Data folder |> Main data folder
|
||||||
data_folder: String, false, def, "data".to_string();
|
data_folder: String, false, def, "data".to_string();
|
||||||
|
|
||||||
/// Database URL
|
/// Database URL
|
||||||
database_url: String, false, auto, |c| format!("{}/{}", c.data_folder, "db.sqlite3");
|
database_url: String, false, auto, |c| format!("{}/{}", c.data_folder, "db.sqlite3");
|
||||||
/// Icon chache folder
|
/// Icon cache folder
|
||||||
icon_cache_folder: String, false, auto, |c| format!("{}/{}", c.data_folder, "icon_cache");
|
icon_cache_folder: String, false, auto, |c| format!("{}/{}", c.data_folder, "icon_cache");
|
||||||
/// Attachments folder
|
/// Attachments folder
|
||||||
attachments_folder: String, false, auto, |c| format!("{}/{}", c.data_folder, "attachments");
|
attachments_folder: String, false, auto, |c| format!("{}/{}", c.data_folder, "attachments");
|
||||||
@@ -235,6 +234,9 @@ make_config! {
|
|||||||
/// Enable web vault
|
/// Enable web vault
|
||||||
web_vault_enabled: bool, false, def, true;
|
web_vault_enabled: bool, false, def, true;
|
||||||
|
|
||||||
|
/// HIBP Api Key |> HaveIBeenPwned API Key, request it here: https://haveibeenpwned.com/API/Key
|
||||||
|
hibp_api_key: Pass, true, option;
|
||||||
|
|
||||||
/// Disable icon downloads |> Set to true to disable icon downloading, this would still serve icons from
|
/// Disable icon downloads |> Set to true to disable icon downloading, this would still serve icons from
|
||||||
/// $ICON_CACHE_FOLDER, but it won't produce any external network request. Needs to set $ICON_CACHE_TTL to 0,
|
/// $ICON_CACHE_FOLDER, but it won't produce any external network request. Needs to set $ICON_CACHE_TTL to 0,
|
||||||
/// otherwise it will delete them and they won't be downloaded again.
|
/// otherwise it will delete them and they won't be downloaded again.
|
||||||
@@ -270,6 +272,10 @@ make_config! {
|
|||||||
/// Note that the checkbox would still be present, but ignored.
|
/// Note that the checkbox would still be present, but ignored.
|
||||||
disable_2fa_remember: bool, true, def, false;
|
disable_2fa_remember: bool, true, def, false;
|
||||||
|
|
||||||
|
/// Require new device emails |> When a user logs in an email is required to be sent.
|
||||||
|
/// If sending the email fails the login attempt will fail.
|
||||||
|
require_device_email: bool, true, def, false;
|
||||||
|
|
||||||
/// Reload templates (Dev) |> When this is set to true, the templates get reloaded with every request.
|
/// Reload templates (Dev) |> When this is set to true, the templates get reloaded with every request.
|
||||||
/// ONLY use this during development, as it can slow down the server
|
/// ONLY use this during development, as it can slow down the server
|
||||||
reload_templates: bool, true, def, false;
|
reload_templates: bool, true, def, false;
|
||||||
@@ -339,6 +345,8 @@ make_config! {
|
|||||||
smtp_username: String, true, option;
|
smtp_username: String, true, option;
|
||||||
/// Password
|
/// Password
|
||||||
smtp_password: Pass, true, option;
|
smtp_password: Pass, true, option;
|
||||||
|
/// Json form auth mechanism |> Defaults for ssl is "Plain" and "Login" and nothing for non-ssl connections. Possible values: ["Plain", "Login", "Xoauth2"]
|
||||||
|
smtp_auth_mechanism: String, true, option;
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -527,6 +535,7 @@ fn load_templates(path: &str) -> Handlebars {
|
|||||||
// First register default templates here
|
// First register default templates here
|
||||||
reg!("email/invite_accepted", ".html");
|
reg!("email/invite_accepted", ".html");
|
||||||
reg!("email/invite_confirmed", ".html");
|
reg!("email/invite_confirmed", ".html");
|
||||||
|
reg!("email/new_device_logged_in", ".html");
|
||||||
reg!("email/pw_hint_none", ".html");
|
reg!("email/pw_hint_none", ".html");
|
||||||
reg!("email/pw_hint_some", ".html");
|
reg!("email/pw_hint_some", ".html");
|
||||||
reg!("email/send_org_invite", ".html");
|
reg!("email/send_org_invite", ".html");
|
||||||
@@ -552,7 +561,7 @@ impl HelperDef for CaseHelper {
|
|||||||
r: &'reg Handlebars,
|
r: &'reg Handlebars,
|
||||||
ctx: &Context,
|
ctx: &Context,
|
||||||
rc: &mut RenderContext<'reg>,
|
rc: &mut RenderContext<'reg>,
|
||||||
out: &mut Output,
|
out: &mut dyn Output,
|
||||||
) -> HelperResult {
|
) -> HelperResult {
|
||||||
let param = h
|
let param = h
|
||||||
.param(0)
|
.param(0)
|
||||||
@@ -576,7 +585,7 @@ impl HelperDef for JsEscapeHelper {
|
|||||||
_: &'reg Handlebars,
|
_: &'reg Handlebars,
|
||||||
_: &Context,
|
_: &Context,
|
||||||
_: &mut RenderContext<'reg>,
|
_: &mut RenderContext<'reg>,
|
||||||
out: &mut Output,
|
out: &mut dyn Output,
|
||||||
) -> HelperResult {
|
) -> HelperResult {
|
||||||
let param = h
|
let param = h
|
||||||
.param(0)
|
.param(0)
|
||||||
|
@@ -2,25 +2,36 @@ use std::ops::Deref;
|
|||||||
|
|
||||||
use diesel::r2d2;
|
use diesel::r2d2;
|
||||||
use diesel::r2d2::ConnectionManager;
|
use diesel::r2d2::ConnectionManager;
|
||||||
use diesel::sqlite::SqliteConnection;
|
|
||||||
use diesel::{Connection as DieselConnection, ConnectionError};
|
use diesel::{Connection as DieselConnection, ConnectionError};
|
||||||
|
|
||||||
use rocket::http::Status;
|
use rocket::http::Status;
|
||||||
use rocket::request::{self, FromRequest};
|
use rocket::request::{self, FromRequest};
|
||||||
use rocket::{Outcome, Request, State};
|
use rocket::{Outcome, Request, State};
|
||||||
|
|
||||||
|
use crate::error::Error;
|
||||||
|
use chrono::prelude::*;
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
use crate::CONFIG;
|
use crate::CONFIG;
|
||||||
|
|
||||||
/// An alias to the database connection used
|
/// An alias to the database connection used
|
||||||
type Connection = SqliteConnection;
|
#[cfg(feature = "sqlite")]
|
||||||
|
type Connection = diesel::sqlite::SqliteConnection;
|
||||||
|
#[cfg(feature = "mysql")]
|
||||||
|
type Connection = diesel::mysql::MysqlConnection;
|
||||||
|
|
||||||
/// An alias to the type for a pool of Diesel SQLite connections.
|
/// An alias to the type for a pool of Diesel connections.
|
||||||
type Pool = r2d2::Pool<ConnectionManager<Connection>>;
|
type Pool = r2d2::Pool<ConnectionManager<Connection>>;
|
||||||
|
|
||||||
/// Connection request guard type: a wrapper around an r2d2 pooled connection.
|
/// Connection request guard type: a wrapper around an r2d2 pooled connection.
|
||||||
pub struct DbConn(pub r2d2::PooledConnection<ConnectionManager<Connection>>);
|
pub struct DbConn(pub r2d2::PooledConnection<ConnectionManager<Connection>>);
|
||||||
|
|
||||||
pub mod models;
|
pub mod models;
|
||||||
|
#[cfg(feature = "sqlite")]
|
||||||
|
#[path = "schemas/sqlite/schema.rs"]
|
||||||
|
pub mod schema;
|
||||||
|
#[cfg(feature = "mysql")]
|
||||||
|
#[path = "schemas/mysql/schema.rs"]
|
||||||
pub mod schema;
|
pub mod schema;
|
||||||
|
|
||||||
/// Initializes a database pool.
|
/// Initializes a database pool.
|
||||||
@@ -34,6 +45,21 @@ pub fn get_connection() -> Result<Connection, ConnectionError> {
|
|||||||
Connection::establish(&CONFIG.database_url())
|
Connection::establish(&CONFIG.database_url())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a back-up of the database using sqlite3
|
||||||
|
pub fn backup_database() -> Result<(), Error> {
|
||||||
|
let now: DateTime<Utc> = Utc::now();
|
||||||
|
let file_date = now.format("%Y%m%d").to_string();
|
||||||
|
let backup_command: String = format!("{}{}{}", ".backup 'db_", file_date, ".sqlite3'");
|
||||||
|
|
||||||
|
Command::new("sqlite3")
|
||||||
|
.current_dir("./data")
|
||||||
|
.args(&["db.sqlite3", &backup_command])
|
||||||
|
.output()
|
||||||
|
.expect("Can't open database, sqlite3 is not available, make sure it's installed and available on the PATH");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Attempts to retrieve a single connection from the managed database pool. If
|
/// Attempts to retrieve a single connection from the managed database pool. If
|
||||||
/// no pool is currently managed, fails with an `InternalServerError` status. If
|
/// no pool is currently managed, fails with an `InternalServerError` status. If
|
||||||
/// no connections are available, fails with a `ServiceUnavailable` status.
|
/// no connections are available, fails with a `ServiceUnavailable` status.
|
||||||
|
@@ -12,7 +12,7 @@ pub struct Attachment {
|
|||||||
pub cipher_uuid: String,
|
pub cipher_uuid: String,
|
||||||
pub file_name: String,
|
pub file_name: String,
|
||||||
pub file_size: i32,
|
pub file_size: i32,
|
||||||
pub key: Option<String>,
|
pub akey: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Local methods
|
/// Local methods
|
||||||
@@ -23,7 +23,7 @@ impl Attachment {
|
|||||||
cipher_uuid,
|
cipher_uuid,
|
||||||
file_name,
|
file_name,
|
||||||
file_size,
|
file_size,
|
||||||
key: None,
|
akey: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,7 +43,7 @@ impl Attachment {
|
|||||||
"FileName": self.file_name,
|
"FileName": self.file_name,
|
||||||
"Size": self.file_size.to_string(),
|
"Size": self.file_size.to_string(),
|
||||||
"SizeName": display_size,
|
"SizeName": display_size,
|
||||||
"Key": self.key,
|
"Key": self.akey,
|
||||||
"Object": "attachment"
|
"Object": "attachment"
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@@ -24,7 +24,7 @@ pub struct Cipher {
|
|||||||
Card = 3,
|
Card = 3,
|
||||||
Identity = 4
|
Identity = 4
|
||||||
*/
|
*/
|
||||||
pub type_: i32,
|
pub atype: i32,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub notes: Option<String>,
|
pub notes: Option<String>,
|
||||||
pub fields: Option<String>,
|
pub fields: Option<String>,
|
||||||
@@ -37,7 +37,7 @@ pub struct Cipher {
|
|||||||
|
|
||||||
/// Local methods
|
/// Local methods
|
||||||
impl Cipher {
|
impl Cipher {
|
||||||
pub fn new(type_: i32, name: String) -> Self {
|
pub fn new(atype: i32, name: String) -> Self {
|
||||||
let now = Utc::now().naive_utc();
|
let now = Utc::now().naive_utc();
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
@@ -48,7 +48,7 @@ impl Cipher {
|
|||||||
user_uuid: None,
|
user_uuid: None,
|
||||||
organization_uuid: None,
|
organization_uuid: None,
|
||||||
|
|
||||||
type_,
|
atype,
|
||||||
favorite: false,
|
favorite: false,
|
||||||
name,
|
name,
|
||||||
|
|
||||||
@@ -77,24 +77,15 @@ impl Cipher {
|
|||||||
let attachments = Attachment::find_by_cipher(&self.uuid, conn);
|
let attachments = Attachment::find_by_cipher(&self.uuid, conn);
|
||||||
let attachments_json: Vec<Value> = attachments.iter().map(|c| c.to_json(host)).collect();
|
let attachments_json: Vec<Value> = attachments.iter().map(|c| c.to_json(host)).collect();
|
||||||
|
|
||||||
let fields_json: Value = if let Some(ref fields) = self.fields {
|
let fields_json = self.fields.as_ref().and_then(|s| serde_json::from_str(s).ok()).unwrap_or(Value::Null);
|
||||||
serde_json::from_str(fields).unwrap()
|
let password_history_json = self.password_history.as_ref().and_then(|s| serde_json::from_str(s).ok()).unwrap_or(Value::Null);
|
||||||
} else {
|
|
||||||
Value::Null
|
|
||||||
};
|
|
||||||
|
|
||||||
let password_history_json: Value = if let Some(ref password_history) = self.password_history {
|
let mut data_json: Value = serde_json::from_str(&self.data).unwrap_or(Value::Null);
|
||||||
serde_json::from_str(password_history).unwrap()
|
|
||||||
} else {
|
|
||||||
Value::Null
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut data_json: Value = serde_json::from_str(&self.data).unwrap();
|
|
||||||
|
|
||||||
// TODO: ******* Backwards compat start **********
|
// TODO: ******* Backwards compat start **********
|
||||||
// To remove backwards compatibility, just remove this entire section
|
// To remove backwards compatibility, just remove this entire section
|
||||||
// and remove the compat code from ciphers::update_cipher_from_data
|
// and remove the compat code from ciphers::update_cipher_from_data
|
||||||
if self.type_ == 1 && data_json["Uris"].is_array() {
|
if self.atype == 1 && data_json["Uris"].is_array() {
|
||||||
let uri = data_json["Uris"][0]["Uri"].clone();
|
let uri = data_json["Uris"][0]["Uri"].clone();
|
||||||
data_json["Uri"] = uri;
|
data_json["Uri"] = uri;
|
||||||
}
|
}
|
||||||
@@ -102,7 +93,7 @@ impl Cipher {
|
|||||||
|
|
||||||
let mut json_object = json!({
|
let mut json_object = json!({
|
||||||
"Id": self.uuid,
|
"Id": self.uuid,
|
||||||
"Type": self.type_,
|
"Type": self.atype,
|
||||||
"RevisionDate": format_date(&self.updated_at),
|
"RevisionDate": format_date(&self.updated_at),
|
||||||
"FolderId": self.get_folder_uuid(&user_uuid, &conn),
|
"FolderId": self.get_folder_uuid(&user_uuid, &conn),
|
||||||
"Favorite": self.favorite,
|
"Favorite": self.favorite,
|
||||||
@@ -123,7 +114,7 @@ impl Cipher {
|
|||||||
"PasswordHistory": password_history_json,
|
"PasswordHistory": password_history_json,
|
||||||
});
|
});
|
||||||
|
|
||||||
let key = match self.type_ {
|
let key = match self.atype {
|
||||||
1 => "Login",
|
1 => "Login",
|
||||||
2 => "SecureNote",
|
2 => "SecureNote",
|
||||||
3 => "Card",
|
3 => "Card",
|
||||||
@@ -237,7 +228,7 @@ impl Cipher {
|
|||||||
// Cipher owner
|
// Cipher owner
|
||||||
users_organizations::access_all.eq(true).or(
|
users_organizations::access_all.eq(true).or(
|
||||||
// access_all in Organization
|
// access_all in Organization
|
||||||
users_organizations::type_.le(UserOrgType::Admin as i32).or(
|
users_organizations::atype.le(UserOrgType::Admin as i32).or(
|
||||||
// Org admin or owner
|
// Org admin or owner
|
||||||
users_collections::user_uuid.eq(user_uuid).and(
|
users_collections::user_uuid.eq(user_uuid).and(
|
||||||
users_collections::read_only.eq(false), //R/W access to collection
|
users_collections::read_only.eq(false), //R/W access to collection
|
||||||
@@ -268,7 +259,7 @@ impl Cipher {
|
|||||||
// Cipher owner
|
// Cipher owner
|
||||||
users_organizations::access_all.eq(true).or(
|
users_organizations::access_all.eq(true).or(
|
||||||
// access_all in Organization
|
// access_all in Organization
|
||||||
users_organizations::type_.le(UserOrgType::Admin as i32).or(
|
users_organizations::atype.le(UserOrgType::Admin as i32).or(
|
||||||
// Org admin or owner
|
// Org admin or owner
|
||||||
users_collections::user_uuid.eq(user_uuid), // Access to Collection
|
users_collections::user_uuid.eq(user_uuid), // Access to Collection
|
||||||
),
|
),
|
||||||
@@ -315,7 +306,7 @@ impl Cipher {
|
|||||||
))
|
))
|
||||||
.filter(ciphers::user_uuid.eq(user_uuid).or( // Cipher owner
|
.filter(ciphers::user_uuid.eq(user_uuid).or( // Cipher owner
|
||||||
users_organizations::access_all.eq(true).or( // access_all in Organization
|
users_organizations::access_all.eq(true).or( // access_all in Organization
|
||||||
users_organizations::type_.le(UserOrgType::Admin as i32).or( // Org admin or owner
|
users_organizations::atype.le(UserOrgType::Admin as i32).or( // Org admin or owner
|
||||||
users_collections::user_uuid.eq(user_uuid).and( // Access to Collection
|
users_collections::user_uuid.eq(user_uuid).and( // Access to Collection
|
||||||
users_organizations::status.eq(UserOrgStatus::Confirmed as i32)
|
users_organizations::status.eq(UserOrgStatus::Confirmed as i32)
|
||||||
)
|
)
|
||||||
@@ -365,7 +356,7 @@ impl Cipher {
|
|||||||
.filter(ciphers_collections::cipher_uuid.eq(&self.uuid))
|
.filter(ciphers_collections::cipher_uuid.eq(&self.uuid))
|
||||||
.filter(users_collections::user_uuid.eq(user_id).or( // User has access to collection
|
.filter(users_collections::user_uuid.eq(user_id).or( // User has access to collection
|
||||||
users_organizations::access_all.eq(true).or( // User has access all
|
users_organizations::access_all.eq(true).or( // User has access all
|
||||||
users_organizations::type_.le(UserOrgType::Admin as i32) // User is admin or owner
|
users_organizations::atype.le(UserOrgType::Admin as i32) // User is admin or owner
|
||||||
)
|
)
|
||||||
))
|
))
|
||||||
.select(ciphers_collections::collection_uuid)
|
.select(ciphers_collections::collection_uuid)
|
||||||
|
@@ -146,7 +146,7 @@ impl Collection {
|
|||||||
.filter(
|
.filter(
|
||||||
users_collections::collection_uuid.eq(uuid).or( // Directly accessed collection
|
users_collections::collection_uuid.eq(uuid).or( // Directly accessed collection
|
||||||
users_organizations::access_all.eq(true).or( // access_all in Organization
|
users_organizations::access_all.eq(true).or( // access_all in Organization
|
||||||
users_organizations::type_.le(UserOrgType::Admin as i32) // Org admin or owner
|
users_organizations::atype.le(UserOrgType::Admin as i32) // Org admin or owner
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
).select(collections::all_columns)
|
).select(collections::all_columns)
|
||||||
|
@@ -15,7 +15,7 @@ pub struct Device {
|
|||||||
|
|
||||||
pub name: String,
|
pub name: String,
|
||||||
/// https://github.com/bitwarden/core/tree/master/src/Core/Enums
|
/// https://github.com/bitwarden/core/tree/master/src/Core/Enums
|
||||||
pub type_: i32,
|
pub atype: i32,
|
||||||
pub push_token: Option<String>,
|
pub push_token: Option<String>,
|
||||||
|
|
||||||
pub refresh_token: String,
|
pub refresh_token: String,
|
||||||
@@ -25,7 +25,7 @@ pub struct Device {
|
|||||||
|
|
||||||
/// Local methods
|
/// Local methods
|
||||||
impl Device {
|
impl Device {
|
||||||
pub fn new(uuid: String, user_uuid: String, name: String, type_: i32) -> Self {
|
pub fn new(uuid: String, user_uuid: String, name: String, atype: i32) -> Self {
|
||||||
let now = Utc::now().naive_utc();
|
let now = Utc::now().naive_utc();
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
@@ -35,7 +35,7 @@ impl Device {
|
|||||||
|
|
||||||
user_uuid,
|
user_uuid,
|
||||||
name,
|
name,
|
||||||
type_,
|
atype,
|
||||||
|
|
||||||
push_token: None,
|
push_token: None,
|
||||||
refresh_token: String::new(),
|
refresh_token: String::new(),
|
||||||
@@ -70,10 +70,10 @@ impl Device {
|
|||||||
let time_now = Utc::now().naive_utc();
|
let time_now = Utc::now().naive_utc();
|
||||||
self.updated_at = time_now;
|
self.updated_at = time_now;
|
||||||
|
|
||||||
let orgowner: Vec<_> = orgs.iter().filter(|o| o.type_ == 0).map(|o| o.org_uuid.clone()).collect();
|
let orgowner: Vec<_> = orgs.iter().filter(|o| o.atype == 0).map(|o| o.org_uuid.clone()).collect();
|
||||||
let orgadmin: Vec<_> = orgs.iter().filter(|o| o.type_ == 1).map(|o| o.org_uuid.clone()).collect();
|
let orgadmin: Vec<_> = orgs.iter().filter(|o| o.atype == 1).map(|o| o.org_uuid.clone()).collect();
|
||||||
let orguser: Vec<_> = orgs.iter().filter(|o| o.type_ == 2).map(|o| o.org_uuid.clone()).collect();
|
let orguser: Vec<_> = orgs.iter().filter(|o| o.atype == 2).map(|o| o.org_uuid.clone()).collect();
|
||||||
let orgmanager: Vec<_> = orgs.iter().filter(|o| o.type_ == 3).map(|o| o.org_uuid.clone()).collect();
|
let orgmanager: Vec<_> = orgs.iter().filter(|o| o.atype == 3).map(|o| o.org_uuid.clone()).collect();
|
||||||
|
|
||||||
|
|
||||||
// Create the JWT claims struct, to send to the client
|
// Create the JWT claims struct, to send to the client
|
||||||
|
@@ -21,9 +21,9 @@ pub struct UserOrganization {
|
|||||||
pub org_uuid: String,
|
pub org_uuid: String,
|
||||||
|
|
||||||
pub access_all: bool,
|
pub access_all: bool,
|
||||||
pub key: String,
|
pub akey: String,
|
||||||
pub status: i32,
|
pub status: i32,
|
||||||
pub type_: i32,
|
pub atype: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum UserOrgStatus {
|
pub enum UserOrgStatus {
|
||||||
@@ -196,9 +196,9 @@ impl UserOrganization {
|
|||||||
org_uuid,
|
org_uuid,
|
||||||
|
|
||||||
access_all: false,
|
access_all: false,
|
||||||
key: String::new(),
|
akey: String::new(),
|
||||||
status: UserOrgStatus::Accepted as i32,
|
status: UserOrgStatus::Accepted as i32,
|
||||||
type_: UserOrgType::User as i32,
|
atype: UserOrgType::User as i32,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -266,9 +266,9 @@ impl UserOrganization {
|
|||||||
"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
|
||||||
|
|
||||||
// These are per user
|
// These are per user
|
||||||
"Key": self.key,
|
"Key": self.akey,
|
||||||
"Status": self.status,
|
"Status": self.status,
|
||||||
"Type": self.type_,
|
"Type": self.atype,
|
||||||
"Enabled": true,
|
"Enabled": true,
|
||||||
|
|
||||||
"Object": "profileOrganization",
|
"Object": "profileOrganization",
|
||||||
@@ -285,7 +285,7 @@ impl UserOrganization {
|
|||||||
"Email": user.email,
|
"Email": user.email,
|
||||||
|
|
||||||
"Status": self.status,
|
"Status": self.status,
|
||||||
"Type": self.type_,
|
"Type": self.atype,
|
||||||
"AccessAll": self.access_all,
|
"AccessAll": self.access_all,
|
||||||
|
|
||||||
"Object": "organizationUserUserDetails",
|
"Object": "organizationUserUserDetails",
|
||||||
@@ -315,7 +315,7 @@ impl UserOrganization {
|
|||||||
"UserId": self.user_uuid,
|
"UserId": self.user_uuid,
|
||||||
|
|
||||||
"Status": self.status,
|
"Status": self.status,
|
||||||
"Type": self.type_,
|
"Type": self.atype,
|
||||||
"AccessAll": self.access_all,
|
"AccessAll": self.access_all,
|
||||||
"Collections": coll_uuids,
|
"Collections": coll_uuids,
|
||||||
|
|
||||||
@@ -357,7 +357,7 @@ impl UserOrganization {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn has_full_access(self) -> bool {
|
pub fn has_full_access(self) -> bool {
|
||||||
self.access_all || self.type_ >= UserOrgType::Admin
|
self.access_all || self.atype >= UserOrgType::Admin
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_by_uuid(uuid: &str, conn: &DbConn) -> Option<Self> {
|
pub fn find_by_uuid(uuid: &str, conn: &DbConn) -> Option<Self> {
|
||||||
@@ -405,10 +405,10 @@ impl UserOrganization {
|
|||||||
.expect("Error loading user organizations")
|
.expect("Error loading user organizations")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_by_org_and_type(org_uuid: &str, type_: i32, conn: &DbConn) -> Vec<Self> {
|
pub fn find_by_org_and_type(org_uuid: &str, atype: i32, conn: &DbConn) -> Vec<Self> {
|
||||||
users_organizations::table
|
users_organizations::table
|
||||||
.filter(users_organizations::org_uuid.eq(org_uuid))
|
.filter(users_organizations::org_uuid.eq(org_uuid))
|
||||||
.filter(users_organizations::type_.eq(type_))
|
.filter(users_organizations::atype.eq(atype))
|
||||||
.load::<Self>(&**conn)
|
.load::<Self>(&**conn)
|
||||||
.expect("Error loading user organizations")
|
.expect("Error loading user organizations")
|
||||||
}
|
}
|
||||||
|
@@ -9,7 +9,7 @@ use super::User;
|
|||||||
pub struct TwoFactor {
|
pub struct TwoFactor {
|
||||||
pub uuid: String,
|
pub uuid: String,
|
||||||
pub user_uuid: String,
|
pub user_uuid: String,
|
||||||
pub type_: i32,
|
pub atype: i32,
|
||||||
pub enabled: bool,
|
pub enabled: bool,
|
||||||
pub data: String,
|
pub data: String,
|
||||||
}
|
}
|
||||||
@@ -32,11 +32,11 @@ pub enum TwoFactorType {
|
|||||||
|
|
||||||
/// Local methods
|
/// Local methods
|
||||||
impl TwoFactor {
|
impl TwoFactor {
|
||||||
pub fn new(user_uuid: String, type_: TwoFactorType, data: String) -> Self {
|
pub fn new(user_uuid: String, atype: TwoFactorType, data: String) -> Self {
|
||||||
Self {
|
Self {
|
||||||
uuid: crate::util::get_uuid(),
|
uuid: crate::util::get_uuid(),
|
||||||
user_uuid,
|
user_uuid,
|
||||||
type_: type_ as i32,
|
atype: atype as i32,
|
||||||
enabled: true,
|
enabled: true,
|
||||||
data,
|
data,
|
||||||
}
|
}
|
||||||
@@ -53,7 +53,7 @@ impl TwoFactor {
|
|||||||
pub fn to_json_list(&self) -> Value {
|
pub fn to_json_list(&self) -> Value {
|
||||||
json!({
|
json!({
|
||||||
"Enabled": self.enabled,
|
"Enabled": self.enabled,
|
||||||
"Type": self.type_,
|
"Type": self.atype,
|
||||||
"Object": "twoFactorProvider"
|
"Object": "twoFactorProvider"
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -85,15 +85,15 @@ impl TwoFactor {
|
|||||||
pub fn find_by_user(user_uuid: &str, conn: &DbConn) -> Vec<Self> {
|
pub fn find_by_user(user_uuid: &str, conn: &DbConn) -> Vec<Self> {
|
||||||
twofactor::table
|
twofactor::table
|
||||||
.filter(twofactor::user_uuid.eq(user_uuid))
|
.filter(twofactor::user_uuid.eq(user_uuid))
|
||||||
.filter(twofactor::type_.lt(1000)) // Filter implementation types
|
.filter(twofactor::atype.lt(1000)) // Filter implementation types
|
||||||
.load::<Self>(&**conn)
|
.load::<Self>(&**conn)
|
||||||
.expect("Error loading twofactor")
|
.expect("Error loading twofactor")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_by_user_and_type(user_uuid: &str, type_: i32, conn: &DbConn) -> Option<Self> {
|
pub fn find_by_user_and_type(user_uuid: &str, atype: i32, conn: &DbConn) -> Option<Self> {
|
||||||
twofactor::table
|
twofactor::table
|
||||||
.filter(twofactor::user_uuid.eq(user_uuid))
|
.filter(twofactor::user_uuid.eq(user_uuid))
|
||||||
.filter(twofactor::type_.eq(type_))
|
.filter(twofactor::atype.eq(atype))
|
||||||
.first::<Self>(&**conn)
|
.first::<Self>(&**conn)
|
||||||
.ok()
|
.ok()
|
||||||
}
|
}
|
||||||
|
@@ -20,7 +20,7 @@ pub struct User {
|
|||||||
pub password_iterations: i32,
|
pub password_iterations: i32,
|
||||||
pub password_hint: Option<String>,
|
pub password_hint: Option<String>,
|
||||||
|
|
||||||
pub key: String,
|
pub akey: String,
|
||||||
pub private_key: Option<String>,
|
pub private_key: Option<String>,
|
||||||
pub public_key: Option<String>,
|
pub public_key: Option<String>,
|
||||||
|
|
||||||
@@ -58,7 +58,7 @@ impl User {
|
|||||||
updated_at: now,
|
updated_at: now,
|
||||||
name: email.clone(),
|
name: email.clone(),
|
||||||
email,
|
email,
|
||||||
key: String::new(),
|
akey: String::new(),
|
||||||
|
|
||||||
password_hash: Vec::new(),
|
password_hash: Vec::new(),
|
||||||
salt: crypto::get_random_64(),
|
salt: crypto::get_random_64(),
|
||||||
@@ -140,7 +140,7 @@ impl User {
|
|||||||
"MasterPasswordHint": self.password_hint,
|
"MasterPasswordHint": self.password_hint,
|
||||||
"Culture": "en-US",
|
"Culture": "en-US",
|
||||||
"TwoFactorEnabled": twofactor_enabled,
|
"TwoFactorEnabled": twofactor_enabled,
|
||||||
"Key": self.key,
|
"Key": self.akey,
|
||||||
"PrivateKey": self.private_key,
|
"PrivateKey": self.private_key,
|
||||||
"SecurityStamp": self.security_stamp,
|
"SecurityStamp": self.security_stamp,
|
||||||
"Organizations": orgs_json,
|
"Organizations": orgs_json,
|
||||||
@@ -163,7 +163,7 @@ impl User {
|
|||||||
|
|
||||||
pub fn delete(self, conn: &DbConn) -> EmptyResult {
|
pub fn delete(self, conn: &DbConn) -> EmptyResult {
|
||||||
for user_org in UserOrganization::find_by_user(&self.uuid, &*conn) {
|
for user_org in UserOrganization::find_by_user(&self.uuid, &*conn) {
|
||||||
if user_org.type_ == UserOrgType::Owner {
|
if user_org.atype == UserOrgType::Owner {
|
||||||
let owner_type = UserOrgType::Owner as i32;
|
let owner_type = UserOrgType::Owner as i32;
|
||||||
if UserOrganization::find_by_org_and_type(&user_org.org_uuid, owner_type, &conn).len() <= 1 {
|
if UserOrganization::find_by_org_and_type(&user_org.org_uuid, owner_type, &conn).len() <= 1 {
|
||||||
err!("Can't delete last owner")
|
err!("Can't delete last owner")
|
||||||
|
172
src/db/schemas/mysql/schema.rs
Normal file
172
src/db/schemas/mysql/schema.rs
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
table! {
|
||||||
|
attachments (id) {
|
||||||
|
id -> Varchar,
|
||||||
|
cipher_uuid -> Varchar,
|
||||||
|
file_name -> Text,
|
||||||
|
file_size -> Integer,
|
||||||
|
akey -> Nullable<Text>,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
table! {
|
||||||
|
ciphers (uuid) {
|
||||||
|
uuid -> Varchar,
|
||||||
|
created_at -> Datetime,
|
||||||
|
updated_at -> Datetime,
|
||||||
|
user_uuid -> Nullable<Varchar>,
|
||||||
|
organization_uuid -> Nullable<Varchar>,
|
||||||
|
atype -> Integer,
|
||||||
|
name -> Text,
|
||||||
|
notes -> Nullable<Text>,
|
||||||
|
fields -> Nullable<Text>,
|
||||||
|
data -> Text,
|
||||||
|
favorite -> Bool,
|
||||||
|
password_history -> Nullable<Text>,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
table! {
|
||||||
|
ciphers_collections (cipher_uuid, collection_uuid) {
|
||||||
|
cipher_uuid -> Varchar,
|
||||||
|
collection_uuid -> Varchar,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
table! {
|
||||||
|
collections (uuid) {
|
||||||
|
uuid -> Varchar,
|
||||||
|
org_uuid -> Varchar,
|
||||||
|
name -> Text,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
table! {
|
||||||
|
devices (uuid) {
|
||||||
|
uuid -> Varchar,
|
||||||
|
created_at -> Datetime,
|
||||||
|
updated_at -> Datetime,
|
||||||
|
user_uuid -> Varchar,
|
||||||
|
name -> Text,
|
||||||
|
atype -> Integer,
|
||||||
|
push_token -> Nullable<Text>,
|
||||||
|
refresh_token -> Text,
|
||||||
|
twofactor_remember -> Nullable<Text>,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
table! {
|
||||||
|
folders (uuid) {
|
||||||
|
uuid -> Varchar,
|
||||||
|
created_at -> Datetime,
|
||||||
|
updated_at -> Datetime,
|
||||||
|
user_uuid -> Varchar,
|
||||||
|
name -> Text,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
table! {
|
||||||
|
folders_ciphers (cipher_uuid, folder_uuid) {
|
||||||
|
cipher_uuid -> Varchar,
|
||||||
|
folder_uuid -> Varchar,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
table! {
|
||||||
|
invitations (email) {
|
||||||
|
email -> Varchar,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
table! {
|
||||||
|
organizations (uuid) {
|
||||||
|
uuid -> Varchar,
|
||||||
|
name -> Text,
|
||||||
|
billing_email -> Text,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
table! {
|
||||||
|
twofactor (uuid) {
|
||||||
|
uuid -> Varchar,
|
||||||
|
user_uuid -> Varchar,
|
||||||
|
atype -> Integer,
|
||||||
|
enabled -> Bool,
|
||||||
|
data -> Text,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
table! {
|
||||||
|
users (uuid) {
|
||||||
|
uuid -> Varchar,
|
||||||
|
created_at -> Datetime,
|
||||||
|
updated_at -> Datetime,
|
||||||
|
email -> Varchar,
|
||||||
|
name -> Text,
|
||||||
|
password_hash -> Blob,
|
||||||
|
salt -> Blob,
|
||||||
|
password_iterations -> Integer,
|
||||||
|
password_hint -> Nullable<Text>,
|
||||||
|
akey -> Text,
|
||||||
|
private_key -> Nullable<Text>,
|
||||||
|
public_key -> Nullable<Text>,
|
||||||
|
totp_secret -> Nullable<Text>,
|
||||||
|
totp_recover -> Nullable<Text>,
|
||||||
|
security_stamp -> Text,
|
||||||
|
equivalent_domains -> Text,
|
||||||
|
excluded_globals -> Text,
|
||||||
|
client_kdf_type -> Integer,
|
||||||
|
client_kdf_iter -> Integer,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
table! {
|
||||||
|
users_collections (user_uuid, collection_uuid) {
|
||||||
|
user_uuid -> Varchar,
|
||||||
|
collection_uuid -> Varchar,
|
||||||
|
read_only -> Bool,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
table! {
|
||||||
|
users_organizations (uuid) {
|
||||||
|
uuid -> Varchar,
|
||||||
|
user_uuid -> Varchar,
|
||||||
|
org_uuid -> Varchar,
|
||||||
|
access_all -> Bool,
|
||||||
|
akey -> Text,
|
||||||
|
status -> Integer,
|
||||||
|
atype -> Integer,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
joinable!(attachments -> ciphers (cipher_uuid));
|
||||||
|
joinable!(ciphers -> organizations (organization_uuid));
|
||||||
|
joinable!(ciphers -> users (user_uuid));
|
||||||
|
joinable!(ciphers_collections -> ciphers (cipher_uuid));
|
||||||
|
joinable!(ciphers_collections -> collections (collection_uuid));
|
||||||
|
joinable!(collections -> organizations (org_uuid));
|
||||||
|
joinable!(devices -> users (user_uuid));
|
||||||
|
joinable!(folders -> users (user_uuid));
|
||||||
|
joinable!(folders_ciphers -> ciphers (cipher_uuid));
|
||||||
|
joinable!(folders_ciphers -> folders (folder_uuid));
|
||||||
|
joinable!(twofactor -> users (user_uuid));
|
||||||
|
joinable!(users_collections -> collections (collection_uuid));
|
||||||
|
joinable!(users_collections -> users (user_uuid));
|
||||||
|
joinable!(users_organizations -> organizations (org_uuid));
|
||||||
|
joinable!(users_organizations -> users (user_uuid));
|
||||||
|
|
||||||
|
allow_tables_to_appear_in_same_query!(
|
||||||
|
attachments,
|
||||||
|
ciphers,
|
||||||
|
ciphers_collections,
|
||||||
|
collections,
|
||||||
|
devices,
|
||||||
|
folders,
|
||||||
|
folders_ciphers,
|
||||||
|
invitations,
|
||||||
|
organizations,
|
||||||
|
twofactor,
|
||||||
|
users,
|
||||||
|
users_collections,
|
||||||
|
users_organizations,
|
||||||
|
);
|
@@ -4,7 +4,7 @@ table! {
|
|||||||
cipher_uuid -> Text,
|
cipher_uuid -> Text,
|
||||||
file_name -> Text,
|
file_name -> Text,
|
||||||
file_size -> Integer,
|
file_size -> Integer,
|
||||||
key -> Nullable<Text>,
|
akey -> Nullable<Text>,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -15,8 +15,7 @@ table! {
|
|||||||
updated_at -> Timestamp,
|
updated_at -> Timestamp,
|
||||||
user_uuid -> Nullable<Text>,
|
user_uuid -> Nullable<Text>,
|
||||||
organization_uuid -> Nullable<Text>,
|
organization_uuid -> Nullable<Text>,
|
||||||
#[sql_name = "type"]
|
atype -> Integer,
|
||||||
type_ -> Integer,
|
|
||||||
name -> Text,
|
name -> Text,
|
||||||
notes -> Nullable<Text>,
|
notes -> Nullable<Text>,
|
||||||
fields -> Nullable<Text>,
|
fields -> Nullable<Text>,
|
||||||
@@ -48,8 +47,7 @@ table! {
|
|||||||
updated_at -> Timestamp,
|
updated_at -> Timestamp,
|
||||||
user_uuid -> Text,
|
user_uuid -> Text,
|
||||||
name -> Text,
|
name -> Text,
|
||||||
#[sql_name = "type"]
|
atype -> Integer,
|
||||||
type_ -> Integer,
|
|
||||||
push_token -> Nullable<Text>,
|
push_token -> Nullable<Text>,
|
||||||
refresh_token -> Text,
|
refresh_token -> Text,
|
||||||
twofactor_remember -> Nullable<Text>,
|
twofactor_remember -> Nullable<Text>,
|
||||||
@@ -91,8 +89,7 @@ table! {
|
|||||||
twofactor (uuid) {
|
twofactor (uuid) {
|
||||||
uuid -> Text,
|
uuid -> Text,
|
||||||
user_uuid -> Text,
|
user_uuid -> Text,
|
||||||
#[sql_name = "type"]
|
atype -> Integer,
|
||||||
type_ -> Integer,
|
|
||||||
enabled -> Bool,
|
enabled -> Bool,
|
||||||
data -> Text,
|
data -> Text,
|
||||||
}
|
}
|
||||||
@@ -109,7 +106,7 @@ table! {
|
|||||||
salt -> Binary,
|
salt -> Binary,
|
||||||
password_iterations -> Integer,
|
password_iterations -> Integer,
|
||||||
password_hint -> Nullable<Text>,
|
password_hint -> Nullable<Text>,
|
||||||
key -> Text,
|
akey -> Text,
|
||||||
private_key -> Nullable<Text>,
|
private_key -> Nullable<Text>,
|
||||||
public_key -> Nullable<Text>,
|
public_key -> Nullable<Text>,
|
||||||
totp_secret -> Nullable<Text>,
|
totp_secret -> Nullable<Text>,
|
||||||
@@ -136,10 +133,9 @@ table! {
|
|||||||
user_uuid -> Text,
|
user_uuid -> Text,
|
||||||
org_uuid -> Text,
|
org_uuid -> Text,
|
||||||
access_all -> Bool,
|
access_all -> Bool,
|
||||||
key -> Text,
|
akey -> Text,
|
||||||
status -> Integer,
|
status -> Integer,
|
||||||
#[sql_name = "type"]
|
atype -> Integer,
|
||||||
type_ -> Integer,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
35
src/mail.rs
35
src/mail.rs
@@ -1,14 +1,17 @@
|
|||||||
use lettre::smtp::authentication::Credentials;
|
use lettre::smtp::authentication::Credentials;
|
||||||
|
use lettre::smtp::authentication::Mechanism as SmtpAuthMechanism;
|
||||||
use lettre::smtp::ConnectionReuseParameters;
|
use lettre::smtp::ConnectionReuseParameters;
|
||||||
use lettre::{ClientSecurity, ClientTlsParameters, SmtpClient, SmtpTransport, Transport};
|
use lettre::{ClientSecurity, ClientTlsParameters, SmtpClient, SmtpTransport, Transport};
|
||||||
use lettre_email::{EmailBuilder, MimeMultipartType, PartBuilder};
|
use lettre_email::{EmailBuilder, MimeMultipartType, PartBuilder};
|
||||||
use native_tls::{Protocol, TlsConnector};
|
use native_tls::{Protocol, TlsConnector};
|
||||||
|
use percent_encoding::{percent_encode, NON_ALPHANUMERIC};
|
||||||
use quoted_printable::encode_to_str;
|
use quoted_printable::encode_to_str;
|
||||||
|
|
||||||
use crate::api::EmptyResult;
|
use crate::api::EmptyResult;
|
||||||
use crate::auth::{encode_jwt, generate_invite_claims};
|
use crate::auth::{encode_jwt, generate_invite_claims};
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
use crate::CONFIG;
|
use crate::CONFIG;
|
||||||
|
use chrono::NaiveDateTime;
|
||||||
|
|
||||||
fn mailer() -> SmtpTransport {
|
fn mailer() -> SmtpTransport {
|
||||||
let host = CONFIG.smtp_host().unwrap();
|
let host = CONFIG.smtp_host().unwrap();
|
||||||
@@ -37,6 +40,17 @@ fn mailer() -> SmtpTransport {
|
|||||||
_ => smtp_client,
|
_ => smtp_client,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let smtp_client = match &CONFIG.smtp_auth_mechanism() {
|
||||||
|
Some(auth_mechanism_json) => {
|
||||||
|
let auth_mechanism = serde_json::from_str::<SmtpAuthMechanism>(&auth_mechanism_json);
|
||||||
|
match auth_mechanism {
|
||||||
|
Ok(auth_mechanism) => smtp_client.authentication_mechanism(auth_mechanism),
|
||||||
|
Err(_) => panic!("Failure to parse mechanism. Is it proper Json? Eg. `\"Plain\"` not `Plain`"),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => smtp_client,
|
||||||
|
};
|
||||||
|
|
||||||
smtp_client
|
smtp_client
|
||||||
.smtp_utf8(true)
|
.smtp_utf8(true)
|
||||||
.connection_reuse(ConnectionReuseParameters::NoReuse)
|
.connection_reuse(ConnectionReuseParameters::NoReuse)
|
||||||
@@ -101,7 +115,7 @@ pub fn send_invite(
|
|||||||
"url": CONFIG.domain(),
|
"url": CONFIG.domain(),
|
||||||
"org_id": org_id.unwrap_or_else(|| "_".to_string()),
|
"org_id": org_id.unwrap_or_else(|| "_".to_string()),
|
||||||
"org_user_id": org_user_id.unwrap_or_else(|| "_".to_string()),
|
"org_user_id": org_user_id.unwrap_or_else(|| "_".to_string()),
|
||||||
"email": address,
|
"email": percent_encode(address.as_bytes(), NON_ALPHANUMERIC).to_string(),
|
||||||
"org_name": org_name,
|
"org_name": org_name,
|
||||||
"token": invite_token,
|
"token": invite_token,
|
||||||
}),
|
}),
|
||||||
@@ -135,6 +149,25 @@ pub fn send_invite_confirmed(address: &str, org_name: &str) -> EmptyResult {
|
|||||||
send_email(&address, &subject, &body_html, &body_text)
|
send_email(&address, &subject, &body_html, &body_text)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn send_new_device_logged_in(address: &str, ip: &str, dt: &NaiveDateTime, device: &str) -> EmptyResult {
|
||||||
|
use crate::util::upcase_first;
|
||||||
|
let device = upcase_first(device);
|
||||||
|
|
||||||
|
let datetime = dt.format("%A, %B %_d, %Y at %H:%M").to_string();
|
||||||
|
|
||||||
|
let (subject, body_html, body_text) = get_text(
|
||||||
|
"email/new_device_logged_in",
|
||||||
|
json!({
|
||||||
|
"url": CONFIG.domain(),
|
||||||
|
"ip": ip,
|
||||||
|
"device": device,
|
||||||
|
"datetime": datetime,
|
||||||
|
}),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
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 html = PartBuilder::new()
|
let html = PartBuilder::new()
|
||||||
.body(encode_to_str(body_html))
|
.body(encode_to_str(body_html))
|
||||||
|
41
src/main.rs
41
src/main.rs
@@ -122,25 +122,28 @@ fn chain_syslog(logger: fern::Dispatch) -> fern::Dispatch {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn check_db() {
|
fn check_db() {
|
||||||
let url = CONFIG.database_url();
|
if cfg!(feature = "sqlite") {
|
||||||
let path = Path::new(&url);
|
let url = CONFIG.database_url();
|
||||||
|
let path = Path::new(&url);
|
||||||
|
|
||||||
if let Some(parent) = path.parent() {
|
if let Some(parent) = path.parent() {
|
||||||
use std::fs;
|
use std::fs;
|
||||||
if fs::create_dir_all(parent).is_err() {
|
if fs::create_dir_all(parent).is_err() {
|
||||||
error!("Error creating database directory");
|
error!("Error creating database directory");
|
||||||
exit(1);
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Turn on WAL in SQLite
|
||||||
|
if CONFIG.enable_db_wal() {
|
||||||
|
use diesel::RunQueryDsl;
|
||||||
|
let connection = db::get_connection().expect("Can't conect to DB");
|
||||||
|
diesel::sql_query("PRAGMA journal_mode=wal")
|
||||||
|
.execute(&connection)
|
||||||
|
.expect("Failed to turn on WAL");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
db::get_connection().expect("Can't connect to DB");
|
||||||
// Turn on WAL in SQLite
|
|
||||||
if CONFIG.enable_db_wal() {
|
|
||||||
use diesel::RunQueryDsl;
|
|
||||||
let connection = db::get_connection().expect("Can't conect to DB");
|
|
||||||
diesel::sql_query("PRAGMA journal_mode=wal")
|
|
||||||
.execute(&connection)
|
|
||||||
.expect("Failed to turn on WAL");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_rsa_keys() {
|
fn check_rsa_keys() {
|
||||||
@@ -207,7 +210,11 @@ fn check_web_vault() {
|
|||||||
// https://docs.rs/diesel_migrations/*/diesel_migrations/macro.embed_migrations.html
|
// https://docs.rs/diesel_migrations/*/diesel_migrations/macro.embed_migrations.html
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
mod migrations {
|
mod migrations {
|
||||||
embed_migrations!();
|
|
||||||
|
#[cfg(feature = "sqlite")]
|
||||||
|
embed_migrations!("migrations/sqlite");
|
||||||
|
#[cfg(feature = "mysql")]
|
||||||
|
embed_migrations!("migrations/mysql");
|
||||||
|
|
||||||
pub fn run_migrations() {
|
pub fn run_migrations() {
|
||||||
// Make sure the database is up to date (create if it doesn't exist, or run the migrations)
|
// Make sure the database is up to date (create if it doesn't exist, or run the migrations)
|
||||||
|
6
src/static/images/error-x.svg
Normal file
6
src/static/images/error-x.svg
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="450" height="450" version="1">
|
||||||
|
<circle cx="225" cy="225" r="225" fill="#C33"/>
|
||||||
|
<g fill="#FFF" stroke="#FFF" stroke-width="70">
|
||||||
|
<path d="M107 110l236 237M107 347l236-237"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 241 B |
@@ -26,9 +26,13 @@
|
|||||||
{{/each}}
|
{{/each}}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div style="flex: 0 0 240px;">
|
<div style="flex: 0 0 300px; font-size: 90%; text-align: right; padding-right: 15px">
|
||||||
<a class="mr-3" href="#" onclick='deauthUser({{jsesc Id}})'>Deauthorize sessions</a>
|
{{#if TwoFactorEnabled}}
|
||||||
<a class="mr-3" href="#" onclick='deleteUser({{jsesc Id}}, {{jsesc Email}})'>Delete User</a>
|
<a class="mr-2" href="#" onclick='remove2fa({{jsesc Id}})'>Remove all 2FA</a>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
<a class="mr-2" href="#" onclick='deauthUser({{jsesc Id}})'>Deauthorize sessions</a>
|
||||||
|
<a class="mr-2" href="#" onclick='deleteUser({{jsesc Id}}, {{jsesc Email}})'>Delete User</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -154,9 +158,23 @@
|
|||||||
{{/unless}}
|
{{/unless}}
|
||||||
{{/each}}
|
{{/each}}
|
||||||
{{/each}}
|
{{/each}}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{{#if can_backup}}
|
||||||
|
<div class="card bg-light mb-3">
|
||||||
|
<div class="card-header"><button type="button" class="btn btn-link collapsed" data-toggle="collapse"
|
||||||
|
data-target="#g_database">Backup Database</button></div>
|
||||||
|
<div id="g_database" class="card-body collapse" data-parent="#config-form">
|
||||||
|
<div class="small mb-3">
|
||||||
|
NOTE: A local installation of sqlite3 is required for this section to work.
|
||||||
|
</div>
|
||||||
|
<button type="button" class="btn btn-primary" onclick="backupDatabase();">Backup Database</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
<button type="submit" class="btn btn-primary">Save</button>
|
<button type="submit" class="btn btn-primary">Save</button>
|
||||||
<button type="button" class="btn btn-danger float-right" onclick="deleteConf();">Reset defaults</button>
|
<button type="button" class="btn btn-danger float-right" onclick="deleteConf();">Reset defaults</button>
|
||||||
</form>
|
</form>
|
||||||
@@ -213,6 +231,12 @@
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
function remove2fa(id) {
|
||||||
|
_post("/admin/users/" + id + "/remove-2fa",
|
||||||
|
"2FA removed correctly",
|
||||||
|
"Error removing 2FA");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
function deauthUser(id) {
|
function deauthUser(id) {
|
||||||
_post("/admin/users/" + id + "/deauth",
|
_post("/admin/users/" + id + "/deauth",
|
||||||
"Sessions deauthorized correctly",
|
"Sessions deauthorized correctly",
|
||||||
@@ -268,6 +292,12 @@
|
|||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
function backupDatabase() {
|
||||||
|
_post("/admin/config/backup_db",
|
||||||
|
"Backup created successfully",
|
||||||
|
"Error creating backup");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
function masterCheck(check_id, inputs_query) {
|
function masterCheck(check_id, inputs_query) {
|
||||||
function toggleEnabled(check_id, inputs_query, enabled) {
|
function toggleEnabled(check_id, inputs_query, enabled) {
|
||||||
$(inputs_query).prop("disabled", !enabled)
|
$(inputs_query).prop("disabled", !enabled)
|
||||||
|
14
src/static/templates/email/new_device_logged_in.hbs
Normal file
14
src/static/templates/email/new_device_logged_in.hbs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
New Device Logged In From {{device}}
|
||||||
|
<!---------------->
|
||||||
|
<html>
|
||||||
|
<p>
|
||||||
|
Your account was just logged into from a new device.
|
||||||
|
|
||||||
|
Date: {{datetime}}
|
||||||
|
IP Address: {{ip}}
|
||||||
|
Device Type: {{device}}
|
||||||
|
|
||||||
|
You can deauthorize all devices that have access to your account from the
|
||||||
|
<a href="{{url}}">web vault</a> under Settings > My Account > Deauthorize Sessions.
|
||||||
|
</p>
|
||||||
|
</html>
|
144
src/static/templates/email/new_device_logged_in.html.hbs
Normal file
144
src/static/templates/email/new_device_logged_in.html.hbs
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
New Device Logged In From {{device}}
|
||||||
|
<!---------------->
|
||||||
|
<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_images/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;" valign="top">
|
||||||
|
Your account was just logged into from a new device.
|
||||||
|
</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" 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">
|
||||||
|
<b>Date</b>: {{datetime}}
|
||||||
|
</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" 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">
|
||||||
|
<b>IP Address:</b> {{ip}}
|
||||||
|
</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" 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">
|
||||||
|
<b>Device Type:</b> {{device}}
|
||||||
|
</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;" 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.
|
||||||
|
</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_images/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>
|
Reference in New Issue
Block a user