diff --git a/.travis.yml b/.travis.yml index abf1db7..0bbb5ae 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,4 +6,6 @@ cache: cargo # Nothing to install install: true -script: cargo build --all-features +script: +- cargo build --features "sqlite enable_syslog" +- cargo build --features "mysql enable_syslog" diff --git a/Cargo.lock b/Cargo.lock index 2e0b695..025ed4c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -116,7 +116,6 @@ dependencies = [ "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "lettre 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", "lettre_email 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", - "libsqlite3-sys 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "multipart 0.16.1 (registry+https://github.com/rust-lang/crates.io-index)", "native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", @@ -429,8 +428,9 @@ dependencies = [ "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "diesel_derives 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libsqlite3-sys 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", + "mysqlclient-sys 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", "r2d2 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)", + "url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1044,16 +1044,6 @@ name = "libc" version = "0.2.55" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "libsqlite3-sys" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cc 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)", - "pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", - "vcpkg 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "lock_api" version = "0.1.5" @@ -1250,6 +1240,15 @@ dependencies = [ "twoway 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "mysqlclient-sys" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", + "vcpkg 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "native-tls" version = "0.2.3" @@ -2861,7 +2860,6 @@ dependencies = [ "checksum lettre 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "646aee0a55545eaffdf0df1ac19b500b51adb3095ec4dfdc704134e56ea23531" "checksum lettre_email 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ae1b3d43e4bb7beb9974a359cbb3ea4f93dfba6c1c0c6e9c9f82e538e0f9ab9f" "checksum libc 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)" = "42914d39aad277d9e176efbdad68acb1d5443ab65afe0e0e4f0d49352a950880" -"checksum libsqlite3-sys 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fd6457c70bbff456d9fe49deaba35ec47c3e598bf8d7950ff0575ceb7a8a6ad1" "checksum lock_api 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "62ebf1391f6acad60e5c8b43706dde4582df75c06698ab44511d15016bc2442c" "checksum log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" "checksum log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c84ec4b527950aa83a329754b01dbe3f58361d1c5efacd1f6d68c494d08a17c6" @@ -2883,6 +2881,7 @@ dependencies = [ "checksum mio-extras 2.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "46e73a04c2fa6250b8d802134d56d554a9ec2922bf977777c805ea5def61ce40" "checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" "checksum multipart 0.16.1 (registry+https://github.com/rust-lang/crates.io-index)" = "136eed74cadb9edd2651ffba732b19a450316b680e4f48d6c79e905799e19d01" +"checksum mysqlclient-sys 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "7e9637d93448044078aaafea7419aed69d301b4a12bcc4aa0ae856eb169bef85" "checksum native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4b2df1a4c22fd44a62147fd8f13dd0f95c9d8ca7b2610299b2a2f9cf8964274e" "checksum net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88" "checksum new_debug_unreachable 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "f40f005c60db6e03bae699e414c58bf9aa7ea02a2d0b9bfbcf19286cc4c82b30" diff --git a/Cargo.toml b/Cargo.toml index f22fb7a..a4dca52 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,8 @@ build = "build.rs" [features] # Empty to keep compatibility, prefer to set USE_SYSLOG=true enable_syslog = [] +mysql = ["diesel/mysql", "diesel_migrations/mysql"] +sqlite = ["diesel/sqlite", "diesel_migrations/sqlite", "libsqlite3-sys"] [target."cfg(not(windows))".dependencies] syslog = "4.0.1" @@ -47,11 +49,11 @@ log = "0.4.6" fern = { version = "0.5.8", features = ["syslog-4"] } # A safe, extensible ORM and Query builder -diesel = { version = "1.4.2", features = ["sqlite", "chrono", "r2d2"] } -diesel_migrations = { version = "1.4.0", features = ["sqlite"] } +diesel = { version = "1.4.2", features = [ "chrono", "r2d2"] } +diesel_migrations = { version = "1.4.0" } -# Bundled SQLite -libsqlite3-sys = { version = "0.12.0", features = ["bundled"] } +# Bundled SQLite +libsqlite3-sys = { version = "0.12.0", features = ["bundled"], optional = true } # Crypto library ring = "0.14.6" diff --git a/Dockerfile b/Dockerfile index 4ddf762..cfa1e29 100644 --- a/Dockerfile +++ b/Dockerfile @@ -23,12 +23,21 @@ RUN ls # we need the Rust compiler and Cargo tooling FROM rust 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\ # sqlite3\ # --no-install-recommends\ # && rm -rf /var/lib/apt/lists/* +# Install MySQL package +RUN apt-get update && apt-get install -y \ + libmariadb-dev\ + --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 @@ -41,7 +50,7 @@ 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 cargo build --features ${DB} --release RUN find . -not -path "./target*" -delete # Copies the complete project @@ -53,7 +62,7 @@ RUN touch src/main.rs # Builds again, this time it'll just be # your actual source files being built -RUN cargo build --release +RUN cargo build --features ${DB} --release ######################## RUNTIME IMAGE ######################## # Create a new stage with a minimal image @@ -68,6 +77,7 @@ ENV ROCKET_WORKERS=10 RUN apt-get update && apt-get install -y\ openssl\ ca-certificates\ + libmariadbclient-dev\ --no-install-recommends\ && rm -rf /var/lib/apt/lists/* diff --git a/Dockerfile.aarch64 b/Dockerfile.aarch64 index 3d84629..c151d9b 100644 --- a/Dockerfile.aarch64 +++ b/Dockerfile.aarch64 @@ -23,6 +23,9 @@ RUN ls # we need the Rust compiler and Cargo tooling FROM rust as build +# set sqlite as default for DB ARG for backward comaptibility +ARG DB=sqlite + RUN apt-get update \ && apt-get install -y \ gcc-aarch64-linux-gnu \ @@ -42,7 +45,8 @@ RUN sed 's/^deb/deb-src/' /etc/apt/sources.list > \ && apt-get update \ && apt-get install -y \ 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 CROSS_COMPILE="1" @@ -55,7 +59,7 @@ COPY . . # Build 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 ######################## # Create a new stage with a minimal image @@ -72,6 +76,7 @@ RUN [ "cross-build-start" ] RUN apt-get update && apt-get install -y\ openssl\ ca-certificates\ + libmariadbclient-dev\ --no-install-recommends\ && rm -rf /var/lib/apt/lists/* diff --git a/Dockerfile.alpine b/Dockerfile.alpine index 2154ca1..8d7eefc 100644 --- a/Dockerfile.alpine +++ b/Dockerfile.alpine @@ -22,8 +22,17 @@ RUN ls # Musl build image for statically compiled binary FROM clux/muslrust:nightly-2018-12-01 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\ + libmysqlclient-dev\ + --no-install-recommends\ + && rm -rf /var/lib/apt/lists/* + WORKDIR /app # Copies the complete project @@ -32,8 +41,11 @@ 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 --release +RUN cargo build --features ${DB} --release ######################## RUNTIME IMAGE ######################## # Create a new stage with a minimal image @@ -47,7 +59,8 @@ ENV SSL_CERT_DIR=/etc/ssl/certs # Install needed libraries RUN apk add \ - openssl\ + openssl \ + mariadb-connector-c \ ca-certificates \ && rm /var/cache/apk/* diff --git a/Dockerfile.armv6 b/Dockerfile.armv6 index 3f4514f..a99f73e 100644 --- a/Dockerfile.armv6 +++ b/Dockerfile.armv6 @@ -23,6 +23,9 @@ RUN ls # we need the Rust compiler and Cargo tooling FROM rust as build +# set sqlite as default for DB ARG for backward comaptibility +ARG DB=sqlite + RUN apt-get update \ && apt-get install -y \ gcc-arm-linux-gnueabi \ @@ -42,7 +45,8 @@ RUN sed 's/^deb/deb-src/' /etc/apt/sources.list > \ && apt-get update \ && apt-get install -y \ 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 CROSS_COMPILE="1" @@ -55,7 +59,7 @@ COPY . . # Build 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 ######################## # Create a new stage with a minimal image @@ -72,6 +76,7 @@ RUN [ "cross-build-start" ] RUN apt-get update && apt-get install -y\ openssl\ ca-certificates\ + libmariadbclient-dev\ --no-install-recommends\ && ln -s /lib/ld-linux-armhf.so.3 /lib/ld-linux.so.3\ && rm -rf /var/lib/apt/lists/* diff --git a/Dockerfile.armv7 b/Dockerfile.armv7 index ef1e347..c93988f 100644 --- a/Dockerfile.armv7 +++ b/Dockerfile.armv7 @@ -23,6 +23,9 @@ RUN ls # we need the Rust compiler and Cargo tooling FROM rust as build +# set sqlite as default for DB ARG for backward comaptibility +ARG DB=sqlite + RUN apt-get update \ && apt-get install -y \ gcc-arm-linux-gnueabihf \ @@ -42,7 +45,9 @@ RUN sed 's/^deb/deb-src/' /etc/apt/sources.list > \ && apt-get update \ && apt-get install -y \ 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 CROSS_COMPILE="1" @@ -55,7 +60,7 @@ COPY . . # Build 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 ######################## # Create a new stage with a minimal image @@ -72,6 +77,7 @@ RUN [ "cross-build-start" ] RUN apt-get update && apt-get install -y\ openssl\ ca-certificates\ + libmariadbclient-dev\ --no-install-recommends\ && rm -rf /var/lib/apt/lists/* diff --git a/azure-pipelines.yml b/azure-pipelines.yml index f2c3e12..30a7ebf 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -8,10 +8,18 @@ steps: echo "##vso[task.prependpath]$HOME/.cargo/bin" displayName: 'Install Rust' +- script: | + sudo apt-get update + sudo apt-get install -y libmysql++-dev + displayName: Install libmysql + - script: | rustc -Vv cargo -V displayName: Query rust and cargo versions -- script : cargo build --all-features - displayName: 'Build project' +- script : cargo build --features "sqlite" + displayName: 'Build project with sqlite backend' + +- script : cargo build --features "mysql" + displayName: 'Build project with mysql backend' diff --git a/migrations/2018-01-14-171611_create_tables/down.sql b/migrations/mysql/2018-01-14-171611_create_tables/down.sql similarity index 100% rename from migrations/2018-01-14-171611_create_tables/down.sql rename to migrations/mysql/2018-01-14-171611_create_tables/down.sql diff --git a/migrations/mysql/2018-01-14-171611_create_tables/up.sql b/migrations/mysql/2018-01-14-171611_create_tables/up.sql new file mode 100644 index 0000000..69d161f --- /dev/null +++ b/migrations/mysql/2018-01-14-171611_create_tables/up.sql @@ -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 +); + diff --git a/migrations/2018-02-17-205753_create_collections_and_orgs/down.sql b/migrations/mysql/2018-02-17-205753_create_collections_and_orgs/down.sql similarity index 100% rename from migrations/2018-02-17-205753_create_collections_and_orgs/down.sql rename to migrations/mysql/2018-02-17-205753_create_collections_and_orgs/down.sql diff --git a/migrations/mysql/2018-02-17-205753_create_collections_and_orgs/up.sql b/migrations/mysql/2018-02-17-205753_create_collections_and_orgs/up.sql new file mode 100644 index 0000000..dd90f9d --- /dev/null +++ b/migrations/mysql/2018-02-17-205753_create_collections_and_orgs/up.sql @@ -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) +); diff --git a/migrations/2018-04-27-155151_create_users_ciphers/down.sql b/migrations/mysql/2018-04-27-155151_create_users_ciphers/down.sql similarity index 100% rename from migrations/2018-04-27-155151_create_users_ciphers/down.sql rename to migrations/mysql/2018-04-27-155151_create_users_ciphers/down.sql diff --git a/migrations/mysql/2018-04-27-155151_create_users_ciphers/up.sql b/migrations/mysql/2018-04-27-155151_create_users_ciphers/up.sql new file mode 100644 index 0000000..573eaf0 --- /dev/null +++ b/migrations/mysql/2018-04-27-155151_create_users_ciphers/up.sql @@ -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 diff --git a/migrations/2018-05-08-161616_create_collection_cipher_map/down.sql b/migrations/mysql/2018-05-08-161616_create_collection_cipher_map/down.sql similarity index 100% rename from migrations/2018-05-08-161616_create_collection_cipher_map/down.sql rename to migrations/mysql/2018-05-08-161616_create_collection_cipher_map/down.sql diff --git a/migrations/mysql/2018-05-08-161616_create_collection_cipher_map/up.sql b/migrations/mysql/2018-05-08-161616_create_collection_cipher_map/up.sql new file mode 100644 index 0000000..a2a9740 --- /dev/null +++ b/migrations/mysql/2018-05-08-161616_create_collection_cipher_map/up.sql @@ -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) +); diff --git a/migrations/2018-05-25-232323_update_attachments_reference/down.sql b/migrations/mysql/2018-05-25-232323_update_attachments_reference/down.sql similarity index 100% rename from migrations/2018-05-25-232323_update_attachments_reference/down.sql rename to migrations/mysql/2018-05-25-232323_update_attachments_reference/down.sql diff --git a/migrations/mysql/2018-05-25-232323_update_attachments_reference/up.sql b/migrations/mysql/2018-05-25-232323_update_attachments_reference/up.sql new file mode 100644 index 0000000..50b2c8e --- /dev/null +++ b/migrations/mysql/2018-05-25-232323_update_attachments_reference/up.sql @@ -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; diff --git a/migrations/2018-06-01-112529_update_devices_twofactor_remember/down.sql b/migrations/mysql/2018-06-01-112529_update_devices_twofactor_remember/down.sql similarity index 100% rename from migrations/2018-06-01-112529_update_devices_twofactor_remember/down.sql rename to migrations/mysql/2018-06-01-112529_update_devices_twofactor_remember/down.sql diff --git a/migrations/2018-06-01-112529_update_devices_twofactor_remember/up.sql b/migrations/mysql/2018-06-01-112529_update_devices_twofactor_remember/up.sql similarity index 100% rename from migrations/2018-06-01-112529_update_devices_twofactor_remember/up.sql rename to migrations/mysql/2018-06-01-112529_update_devices_twofactor_remember/up.sql diff --git a/migrations/2018-07-11-181453_create_u2f_twofactor/down.sql b/migrations/mysql/2018-07-11-181453_create_u2f_twofactor/down.sql similarity index 100% rename from migrations/2018-07-11-181453_create_u2f_twofactor/down.sql rename to migrations/mysql/2018-07-11-181453_create_u2f_twofactor/down.sql diff --git a/migrations/mysql/2018-07-11-181453_create_u2f_twofactor/up.sql b/migrations/mysql/2018-07-11-181453_create_u2f_twofactor/up.sql new file mode 100644 index 0000000..ab6c753 --- /dev/null +++ b/migrations/mysql/2018-07-11-181453_create_u2f_twofactor/up.sql @@ -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 diff --git a/migrations/2018-08-27-172114_update_ciphers/down.sql b/migrations/mysql/2018-08-27-172114_update_ciphers/down.sql similarity index 100% rename from migrations/2018-08-27-172114_update_ciphers/down.sql rename to migrations/mysql/2018-08-27-172114_update_ciphers/down.sql diff --git a/migrations/2018-08-27-172114_update_ciphers/up.sql b/migrations/mysql/2018-08-27-172114_update_ciphers/up.sql similarity index 100% rename from migrations/2018-08-27-172114_update_ciphers/up.sql rename to migrations/mysql/2018-08-27-172114_update_ciphers/up.sql diff --git a/migrations/2018-09-10-111213_add_invites/down.sql b/migrations/mysql/2018-09-10-111213_add_invites/down.sql similarity index 100% rename from migrations/2018-09-10-111213_add_invites/down.sql rename to migrations/mysql/2018-09-10-111213_add_invites/down.sql diff --git a/migrations/mysql/2018-09-10-111213_add_invites/up.sql b/migrations/mysql/2018-09-10-111213_add_invites/up.sql new file mode 100644 index 0000000..4245aa8 --- /dev/null +++ b/migrations/mysql/2018-09-10-111213_add_invites/up.sql @@ -0,0 +1,3 @@ +CREATE TABLE invitations ( + email VARCHAR(255) NOT NULL PRIMARY KEY +); diff --git a/migrations/2018-09-19-144557_add_kdf_columns/down.sql b/migrations/mysql/2018-09-19-144557_add_kdf_columns/down.sql similarity index 100% rename from migrations/2018-09-19-144557_add_kdf_columns/down.sql rename to migrations/mysql/2018-09-19-144557_add_kdf_columns/down.sql diff --git a/migrations/2018-09-19-144557_add_kdf_columns/up.sql b/migrations/mysql/2018-09-19-144557_add_kdf_columns/up.sql similarity index 100% rename from migrations/2018-09-19-144557_add_kdf_columns/up.sql rename to migrations/mysql/2018-09-19-144557_add_kdf_columns/up.sql diff --git a/migrations/2018-11-27-152651_add_att_key_columns/down.sql b/migrations/mysql/2018-11-27-152651_add_att_key_columns/down.sql similarity index 100% rename from migrations/2018-11-27-152651_add_att_key_columns/down.sql rename to migrations/mysql/2018-11-27-152651_add_att_key_columns/down.sql diff --git a/migrations/mysql/2018-11-27-152651_add_att_key_columns/up.sql b/migrations/mysql/2018-11-27-152651_add_att_key_columns/up.sql new file mode 100644 index 0000000..e77599f --- /dev/null +++ b/migrations/mysql/2018-11-27-152651_add_att_key_columns/up.sql @@ -0,0 +1,3 @@ +ALTER TABLE attachments + ADD COLUMN + `key` TEXT; diff --git a/migrations/mysql/2019-05-26-216651_rename_key_and_type_columns/down.sql b/migrations/mysql/2019-05-26-216651_rename_key_and_type_columns/down.sql new file mode 100644 index 0000000..8a87d19 --- /dev/null +++ b/migrations/mysql/2019-05-26-216651_rename_key_and_type_columns/down.sql @@ -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; \ No newline at end of file diff --git a/migrations/mysql/2019-05-26-216651_rename_key_and_type_columns/up.sql b/migrations/mysql/2019-05-26-216651_rename_key_and_type_columns/up.sql new file mode 100644 index 0000000..cee0351 --- /dev/null +++ b/migrations/mysql/2019-05-26-216651_rename_key_and_type_columns/up.sql @@ -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; \ No newline at end of file diff --git a/migrations/sqlite/2018-01-14-171611_create_tables/down.sql b/migrations/sqlite/2018-01-14-171611_create_tables/down.sql new file mode 100644 index 0000000..1025a2c --- /dev/null +++ b/migrations/sqlite/2018-01-14-171611_create_tables/down.sql @@ -0,0 +1,9 @@ +DROP TABLE users; + +DROP TABLE devices; + +DROP TABLE ciphers; + +DROP TABLE attachments; + +DROP TABLE folders; \ No newline at end of file diff --git a/migrations/2018-01-14-171611_create_tables/up.sql b/migrations/sqlite/2018-01-14-171611_create_tables/up.sql similarity index 100% rename from migrations/2018-01-14-171611_create_tables/up.sql rename to migrations/sqlite/2018-01-14-171611_create_tables/up.sql diff --git a/migrations/sqlite/2018-02-17-205753_create_collections_and_orgs/down.sql b/migrations/sqlite/2018-02-17-205753_create_collections_and_orgs/down.sql new file mode 100644 index 0000000..b6446cd --- /dev/null +++ b/migrations/sqlite/2018-02-17-205753_create_collections_and_orgs/down.sql @@ -0,0 +1,8 @@ +DROP TABLE collections; + +DROP TABLE organizations; + + +DROP TABLE users_collections; + +DROP TABLE users_organizations; diff --git a/migrations/2018-02-17-205753_create_collections_and_orgs/up.sql b/migrations/sqlite/2018-02-17-205753_create_collections_and_orgs/up.sql similarity index 100% rename from migrations/2018-02-17-205753_create_collections_and_orgs/up.sql rename to migrations/sqlite/2018-02-17-205753_create_collections_and_orgs/up.sql diff --git a/migrations/sqlite/2018-04-27-155151_create_users_ciphers/down.sql b/migrations/sqlite/2018-04-27-155151_create_users_ciphers/down.sql new file mode 100644 index 0000000..e69de29 diff --git a/migrations/2018-04-27-155151_create_users_ciphers/up.sql b/migrations/sqlite/2018-04-27-155151_create_users_ciphers/up.sql similarity index 100% rename from migrations/2018-04-27-155151_create_users_ciphers/up.sql rename to migrations/sqlite/2018-04-27-155151_create_users_ciphers/up.sql diff --git a/migrations/sqlite/2018-05-08-161616_create_collection_cipher_map/down.sql b/migrations/sqlite/2018-05-08-161616_create_collection_cipher_map/down.sql new file mode 100644 index 0000000..ba973f4 --- /dev/null +++ b/migrations/sqlite/2018-05-08-161616_create_collection_cipher_map/down.sql @@ -0,0 +1 @@ +DROP TABLE ciphers_collections; \ No newline at end of file diff --git a/migrations/2018-05-08-161616_create_collection_cipher_map/up.sql b/migrations/sqlite/2018-05-08-161616_create_collection_cipher_map/up.sql similarity index 100% rename from migrations/2018-05-08-161616_create_collection_cipher_map/up.sql rename to migrations/sqlite/2018-05-08-161616_create_collection_cipher_map/up.sql diff --git a/migrations/sqlite/2018-05-25-232323_update_attachments_reference/down.sql b/migrations/sqlite/2018-05-25-232323_update_attachments_reference/down.sql new file mode 100644 index 0000000..e69de29 diff --git a/migrations/2018-05-25-232323_update_attachments_reference/up.sql b/migrations/sqlite/2018-05-25-232323_update_attachments_reference/up.sql similarity index 100% rename from migrations/2018-05-25-232323_update_attachments_reference/up.sql rename to migrations/sqlite/2018-05-25-232323_update_attachments_reference/up.sql diff --git a/migrations/sqlite/2018-06-01-112529_update_devices_twofactor_remember/down.sql b/migrations/sqlite/2018-06-01-112529_update_devices_twofactor_remember/down.sql new file mode 100644 index 0000000..291a97c --- /dev/null +++ b/migrations/sqlite/2018-06-01-112529_update_devices_twofactor_remember/down.sql @@ -0,0 +1 @@ +-- This file should undo anything in `up.sql` \ No newline at end of file diff --git a/migrations/sqlite/2018-06-01-112529_update_devices_twofactor_remember/up.sql b/migrations/sqlite/2018-06-01-112529_update_devices_twofactor_remember/up.sql new file mode 100644 index 0000000..aaad8ea --- /dev/null +++ b/migrations/sqlite/2018-06-01-112529_update_devices_twofactor_remember/up.sql @@ -0,0 +1,3 @@ +ALTER TABLE devices + ADD COLUMN + twofactor_remember TEXT; \ No newline at end of file diff --git a/migrations/sqlite/2018-07-11-181453_create_u2f_twofactor/down.sql b/migrations/sqlite/2018-07-11-181453_create_u2f_twofactor/down.sql new file mode 100644 index 0000000..84b6ee6 --- /dev/null +++ b/migrations/sqlite/2018-07-11-181453_create_u2f_twofactor/down.sql @@ -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; \ No newline at end of file diff --git a/migrations/2018-07-11-181453_create_u2f_twofactor/up.sql b/migrations/sqlite/2018-07-11-181453_create_u2f_twofactor/up.sql similarity index 100% rename from migrations/2018-07-11-181453_create_u2f_twofactor/up.sql rename to migrations/sqlite/2018-07-11-181453_create_u2f_twofactor/up.sql diff --git a/migrations/sqlite/2018-08-27-172114_update_ciphers/down.sql b/migrations/sqlite/2018-08-27-172114_update_ciphers/down.sql new file mode 100644 index 0000000..e69de29 diff --git a/migrations/sqlite/2018-08-27-172114_update_ciphers/up.sql b/migrations/sqlite/2018-08-27-172114_update_ciphers/up.sql new file mode 100644 index 0000000..624efd6 --- /dev/null +++ b/migrations/sqlite/2018-08-27-172114_update_ciphers/up.sql @@ -0,0 +1,3 @@ +ALTER TABLE ciphers + ADD COLUMN + password_history TEXT; \ No newline at end of file diff --git a/migrations/sqlite/2018-09-10-111213_add_invites/down.sql b/migrations/sqlite/2018-09-10-111213_add_invites/down.sql new file mode 100644 index 0000000..af3776c --- /dev/null +++ b/migrations/sqlite/2018-09-10-111213_add_invites/down.sql @@ -0,0 +1 @@ +DROP TABLE invitations; \ No newline at end of file diff --git a/migrations/2018-09-10-111213_add_invites/up.sql b/migrations/sqlite/2018-09-10-111213_add_invites/up.sql similarity index 100% rename from migrations/2018-09-10-111213_add_invites/up.sql rename to migrations/sqlite/2018-09-10-111213_add_invites/up.sql diff --git a/migrations/sqlite/2018-09-19-144557_add_kdf_columns/down.sql b/migrations/sqlite/2018-09-19-144557_add_kdf_columns/down.sql new file mode 100644 index 0000000..e69de29 diff --git a/migrations/sqlite/2018-09-19-144557_add_kdf_columns/up.sql b/migrations/sqlite/2018-09-19-144557_add_kdf_columns/up.sql new file mode 100644 index 0000000..bd98f40 --- /dev/null +++ b/migrations/sqlite/2018-09-19-144557_add_kdf_columns/up.sql @@ -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; diff --git a/migrations/sqlite/2018-11-27-152651_add_att_key_columns/down.sql b/migrations/sqlite/2018-11-27-152651_add_att_key_columns/down.sql new file mode 100644 index 0000000..e69de29 diff --git a/migrations/2018-11-27-152651_add_att_key_columns/up.sql b/migrations/sqlite/2018-11-27-152651_add_att_key_columns/up.sql similarity index 100% rename from migrations/2018-11-27-152651_add_att_key_columns/up.sql rename to migrations/sqlite/2018-11-27-152651_add_att_key_columns/up.sql diff --git a/migrations/sqlite/2019-05-26-216651_rename_key_and_type_columns/down.sql b/migrations/sqlite/2019-05-26-216651_rename_key_and_type_columns/down.sql new file mode 100644 index 0000000..4e6902a --- /dev/null +++ b/migrations/sqlite/2019-05-26-216651_rename_key_and_type_columns/down.sql @@ -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; \ No newline at end of file diff --git a/migrations/sqlite/2019-05-26-216651_rename_key_and_type_columns/up.sql b/migrations/sqlite/2019-05-26-216651_rename_key_and_type_columns/up.sql new file mode 100644 index 0000000..ab2c5b5 --- /dev/null +++ b/migrations/sqlite/2019-05-26-216651_rename_key_and_type_columns/up.sql @@ -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; \ No newline at end of file diff --git a/src/api/core/accounts.rs b/src/api/core/accounts.rs index bc2d286..3b16514 100644 --- a/src/api/core/accounts.rs +++ b/src/api/core/accounts.rs @@ -106,7 +106,7 @@ fn register(data: JsonUpcase, conn: DbConn) -> EmptyResult { } user.set_password(&data.MasterPasswordHash); - user.key = data.Key; + user.akey = data.Key; // Add extra fields if present if let Some(name) = data.Name { @@ -204,7 +204,7 @@ fn post_password(data: JsonUpcase, headers: Headers, conn: DbCon } user.set_password(&data.NewMasterPasswordHash); - user.key = data.Key; + user.akey = data.Key; user.save(&conn) } @@ -231,7 +231,7 @@ fn post_kdf(data: JsonUpcase, headers: Headers, conn: DbConn) -> user.client_kdf_iter = data.KdfIterations; user.client_kdf_type = data.Kdf; user.set_password(&data.NewMasterPasswordHash); - user.key = data.Key; + user.akey = data.Key; user.save(&conn) } @@ -306,7 +306,7 @@ fn post_rotatekey(data: JsonUpcase, headers: Headers, conn: DbConn, nt: // Update user data let mut user = headers.user; - user.key = data.Key; + user.akey = data.Key; user.private_key = Some(data.PrivateKey); user.reset_security_stamp(); @@ -377,7 +377,7 @@ fn post_email(data: JsonUpcase, headers: Headers, conn: DbConn) user.email = data.NewEmail; user.set_password(&data.NewMasterPasswordHash); - user.key = data.Key; + user.akey = data.Key; user.save(&conn) } diff --git a/src/api/core/ciphers.rs b/src/api/core/ciphers.rs index f44ad51..05ee7e1 100644 --- a/src/api/core/ciphers.rs +++ b/src/api/core/ciphers.rs @@ -267,7 +267,7 @@ pub fn update_cipher_from_data( 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.save(&conn)?; @@ -691,7 +691,7 @@ fn post_attachment( }; 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"); } _ => 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) { None => err!("You don't have permission to purge the organization vault"), 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)?; Collection::delete_all_by_organization(&org_data.org_id, &conn)?; nt.send_user_update(UpdateType::Vault, &user); diff --git a/src/api/core/mod.rs b/src/api/core/mod.rs index 4abe31a..6be0f9c 100644 --- a/src/api/core/mod.rs +++ b/src/api/core/mod.rs @@ -63,7 +63,7 @@ fn put_device_token(uuid: String, data: JsonUpcase, headers: Headers) -> Ok(Json(json!({ "Id": headers.device.uuid, "Name": headers.device.name, - "Type": headers.device.type_, + "Type": headers.device.atype, "Identifier": headers.device.uuid, "CreationDate": crate::util::format_date(&headers.device.created_at), }))) diff --git a/src/api/core/organizations.rs b/src/api/core/organizations.rs index 4396733..0a6a9db 100644 --- a/src/api/core/organizations.rs +++ b/src/api/core/organizations.rs @@ -80,9 +80,9 @@ fn create_organization(headers: Headers, data: JsonUpcase, conn: DbConn let mut user_org = UserOrganization::new(headers.user.uuid.clone(), org.uuid.clone()); 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.type_ = UserOrgType::Owner as i32; + user_org.atype = UserOrgType::Owner as i32; user_org.status = UserOrgStatus::Confirmed as i32; 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) { None => err!("User not part of organization"), Some(user_org) => { - if user_org.type_ == UserOrgType::Owner { + if user_org.atype == UserOrgType::Owner { let num_owners = 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, headers: AdminHeade let mut new_user = UserOrganization::new(user.uuid.clone(), org_id.clone()); let access_all = data.AccessAll.unwrap_or(false); new_user.access_all = access_all; - new_user.type_ = new_type; + new_user.atype = new_type; new_user.status = user_org_status; // 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"), }; - 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") } @@ -666,7 +666,7 @@ fn confirm_invite( } 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(), None => err!("Invalid key provided"), }; @@ -735,18 +735,18 @@ fn edit_user( None => err!("The specified user isn't member of the organization"), }; - if new_type != user_to_edit.type_ - && (user_to_edit.type_ >= UserOrgType::Admin || new_type >= UserOrgType::Admin) + if new_type != user_to_edit.atype + && (user_to_edit.atype >= UserOrgType::Admin || new_type >= UserOrgType::Admin) && headers.org_user_type != UserOrgType::Owner { 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") } - 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 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.type_ = new_type as i32; + user_to_edit.atype = new_type as i32; // Delete all the odd collections 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"), }; - 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") } - if user_to_delete.type_ == UserOrgType::Owner { + if user_to_delete.atype == UserOrgType::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(); @@ -842,7 +842,7 @@ fn post_org_import( 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") } diff --git a/src/api/identity.rs b/src/api/identity.rs index 0645dad..3602dc9 100644 --- a/src/api/identity.rs +++ b/src/api/identity.rs @@ -68,7 +68,7 @@ fn _refresh_login(data: ConnectData, conn: DbConn) -> JsonResult { "expires_in": expires_in, "token_type": "Bearer", "refresh_token": device.refresh_token, - "Key": user.key, + "Key": user.akey, "PrivateKey": user.private_key, }))) } @@ -132,7 +132,7 @@ fn _password_login(data: ConnectData, conn: DbConn, ip: ClientIp) -> JsonResult "expires_in": expires_in, "token_type": "Bearer", "refresh_token": device.refresh_token, - "Key": user.key, + "Key": user.akey, "PrivateKey": user.private_key, //"TwoFactorToken": "11122233333444555666777888999" }); @@ -158,7 +158,7 @@ fn twofactor_auth( 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 twofactor_code = match data.two_factor_token { @@ -166,7 +166,7 @@ fn twofactor_auth( 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::crypto::ct_eq; diff --git a/src/auth.rs b/src/auth.rs index 7c65dea..eab1e90 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -286,7 +286,7 @@ impl<'a, 'r> FromRequest<'a, 'r> for OrgHeaders { device: headers.device, user, 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 } else { // This should only happen if the DB is corrupted diff --git a/src/config.rs b/src/config.rs index 6fb3c92..b17df57 100644 --- a/src/config.rs +++ b/src/config.rs @@ -202,7 +202,6 @@ make_config! { folders { /// Data folder |> Main data folder data_folder: String, false, def, "data".to_string(); - /// Database URL database_url: String, false, auto, |c| format!("{}/{}", c.data_folder, "db.sqlite3"); /// Icon chache folder diff --git a/src/db/mod.rs b/src/db/mod.rs index 38bd756..2751aea 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -2,7 +2,6 @@ use std::ops::Deref; use diesel::r2d2; use diesel::r2d2::ConnectionManager; -use diesel::sqlite::SqliteConnection; use diesel::{Connection as DieselConnection, ConnectionError}; use rocket::http::Status; @@ -17,16 +16,25 @@ use crate::error::Error; use crate::CONFIG; /// 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>; /// Connection request guard type: a wrapper around an r2d2 pooled connection. pub struct DbConn(pub r2d2::PooledConnection>); 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; + /// Initializes a database pool. pub fn init_pool() -> Pool { @@ -36,7 +44,9 @@ pub fn init_pool() -> Pool { } pub fn get_connection() -> Result { - Connection::establish(&CONFIG.database_url()) + let url = CONFIG.database_url(); + println!("{}", url.to_string()); + Connection::establish(&url) } /// Creates a back-up of the database using sqlite3 diff --git a/src/db/models/attachment.rs b/src/db/models/attachment.rs index b27704f..030400e 100644 --- a/src/db/models/attachment.rs +++ b/src/db/models/attachment.rs @@ -12,7 +12,7 @@ pub struct Attachment { pub cipher_uuid: String, pub file_name: String, pub file_size: i32, - pub key: Option, + pub akey: Option, } /// Local methods @@ -23,7 +23,7 @@ impl Attachment { cipher_uuid, file_name, file_size, - key: None, + akey: None, } } @@ -43,7 +43,7 @@ impl Attachment { "FileName": self.file_name, "Size": self.file_size.to_string(), "SizeName": display_size, - "Key": self.key, + "Key": self.akey, "Object": "attachment" }) } diff --git a/src/db/models/cipher.rs b/src/db/models/cipher.rs index 21cabb9..0bd7673 100644 --- a/src/db/models/cipher.rs +++ b/src/db/models/cipher.rs @@ -24,7 +24,7 @@ pub struct Cipher { Card = 3, Identity = 4 */ - pub type_: i32, + pub atype: i32, pub name: String, pub notes: Option, pub fields: Option, @@ -37,7 +37,7 @@ pub struct Cipher { /// Local methods impl Cipher { - pub fn new(type_: i32, name: String) -> Self { + pub fn new(atype: i32, name: String) -> Self { let now = Utc::now().naive_utc(); Self { @@ -48,7 +48,7 @@ impl Cipher { user_uuid: None, organization_uuid: None, - type_, + atype, favorite: false, name, @@ -94,7 +94,7 @@ impl Cipher { // TODO: ******* Backwards compat start ********** // To remove backwards compatibility, just remove this entire section // 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(); data_json["Uri"] = uri; } @@ -102,7 +102,7 @@ impl Cipher { let mut json_object = json!({ "Id": self.uuid, - "Type": self.type_, + "Type": self.atype, "RevisionDate": format_date(&self.updated_at), "FolderId": self.get_folder_uuid(&user_uuid, &conn), "Favorite": self.favorite, @@ -123,7 +123,7 @@ impl Cipher { "PasswordHistory": password_history_json, }); - let key = match self.type_ { + let key = match self.atype { 1 => "Login", 2 => "SecureNote", 3 => "Card", @@ -237,7 +237,7 @@ impl Cipher { // Cipher owner users_organizations::access_all.eq(true).or( // 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 users_collections::user_uuid.eq(user_uuid).and( users_collections::read_only.eq(false), //R/W access to collection @@ -268,7 +268,7 @@ impl Cipher { // Cipher owner users_organizations::access_all.eq(true).or( // 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 users_collections::user_uuid.eq(user_uuid), // Access to Collection ), @@ -315,7 +315,7 @@ impl Cipher { )) .filter(ciphers::user_uuid.eq(user_uuid).or( // Cipher owner 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_organizations::status.eq(UserOrgStatus::Confirmed as i32) ) @@ -365,7 +365,7 @@ impl Cipher { .filter(ciphers_collections::cipher_uuid.eq(&self.uuid)) .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::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) diff --git a/src/db/models/collection.rs b/src/db/models/collection.rs index c6047aa..e2b58cf 100644 --- a/src/db/models/collection.rs +++ b/src/db/models/collection.rs @@ -146,7 +146,7 @@ impl Collection { .filter( users_collections::collection_uuid.eq(uuid).or( // Directly accessed collection 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) diff --git a/src/db/models/device.rs b/src/db/models/device.rs index 157bd9a..83824ba 100644 --- a/src/db/models/device.rs +++ b/src/db/models/device.rs @@ -15,7 +15,7 @@ pub struct Device { pub name: String, /// https://github.com/bitwarden/core/tree/master/src/Core/Enums - pub type_: i32, + pub atype: i32, pub push_token: Option, pub refresh_token: String, @@ -25,7 +25,7 @@ pub struct Device { /// Local methods 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(); Self { @@ -35,7 +35,7 @@ impl Device { user_uuid, name, - type_, + atype, push_token: None, refresh_token: String::new(), @@ -70,10 +70,10 @@ impl Device { let time_now = Utc::now().naive_utc(); self.updated_at = time_now; - let orgowner: Vec<_> = orgs.iter().filter(|o| o.type_ == 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 orguser: Vec<_> = orgs.iter().filter(|o| o.type_ == 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 orgowner: Vec<_> = orgs.iter().filter(|o| o.atype == 0).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.atype == 2).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 diff --git a/src/db/models/organization.rs b/src/db/models/organization.rs index 491c4fc..2e5ec0f 100644 --- a/src/db/models/organization.rs +++ b/src/db/models/organization.rs @@ -21,9 +21,9 @@ pub struct UserOrganization { pub org_uuid: String, pub access_all: bool, - pub key: String, + pub akey: String, pub status: i32, - pub type_: i32, + pub atype: i32, } pub enum UserOrgStatus { @@ -196,9 +196,9 @@ impl UserOrganization { org_uuid, access_all: false, - key: String::new(), + akey: String::new(), 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 // These are per user - "Key": self.key, + "Key": self.akey, "Status": self.status, - "Type": self.type_, + "Type": self.atype, "Enabled": true, "Object": "profileOrganization", @@ -285,7 +285,7 @@ impl UserOrganization { "Email": user.email, "Status": self.status, - "Type": self.type_, + "Type": self.atype, "AccessAll": self.access_all, "Object": "organizationUserUserDetails", @@ -315,7 +315,7 @@ impl UserOrganization { "UserId": self.user_uuid, "Status": self.status, - "Type": self.type_, + "Type": self.atype, "AccessAll": self.access_all, "Collections": coll_uuids, @@ -357,7 +357,7 @@ impl UserOrganization { } 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 { @@ -405,10 +405,10 @@ impl UserOrganization { .expect("Error loading user organizations") } - pub fn find_by_org_and_type(org_uuid: &str, type_: i32, conn: &DbConn) -> Vec { + pub fn find_by_org_and_type(org_uuid: &str, atype: i32, conn: &DbConn) -> Vec { users_organizations::table .filter(users_organizations::org_uuid.eq(org_uuid)) - .filter(users_organizations::type_.eq(type_)) + .filter(users_organizations::atype.eq(atype)) .load::(&**conn) .expect("Error loading user organizations") } diff --git a/src/db/models/two_factor.rs b/src/db/models/two_factor.rs index fc48313..30a7b47 100644 --- a/src/db/models/two_factor.rs +++ b/src/db/models/two_factor.rs @@ -9,7 +9,7 @@ use super::User; pub struct TwoFactor { pub uuid: String, pub user_uuid: String, - pub type_: i32, + pub atype: i32, pub enabled: bool, pub data: String, } @@ -32,11 +32,11 @@ pub enum TwoFactorType { /// Local methods 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 { uuid: crate::util::get_uuid(), user_uuid, - type_: type_ as i32, + atype: atype as i32, enabled: true, data, } @@ -53,7 +53,7 @@ impl TwoFactor { pub fn to_json_list(&self) -> Value { json!({ "Enabled": self.enabled, - "Type": self.type_, + "Type": self.atype, "Object": "twoFactorProvider" }) } @@ -85,15 +85,15 @@ impl TwoFactor { pub fn find_by_user(user_uuid: &str, conn: &DbConn) -> Vec { twofactor::table .filter(twofactor::user_uuid.eq(user_uuid)) - .filter(twofactor::type_.lt(1000)) // Filter implementation types + .filter(twofactor::atype.lt(1000)) // Filter implementation types .load::(&**conn) .expect("Error loading twofactor") } - pub fn find_by_user_and_type(user_uuid: &str, type_: i32, conn: &DbConn) -> Option { + pub fn find_by_user_and_type(user_uuid: &str, atype: i32, conn: &DbConn) -> Option { twofactor::table .filter(twofactor::user_uuid.eq(user_uuid)) - .filter(twofactor::type_.eq(type_)) + .filter(twofactor::atype.eq(atype)) .first::(&**conn) .ok() } diff --git a/src/db/models/user.rs b/src/db/models/user.rs index 150a97b..c0d2f73 100644 --- a/src/db/models/user.rs +++ b/src/db/models/user.rs @@ -20,7 +20,7 @@ pub struct User { pub password_iterations: i32, pub password_hint: Option, - pub key: String, + pub akey: String, pub private_key: Option, pub public_key: Option, @@ -58,7 +58,7 @@ impl User { updated_at: now, name: email.clone(), email, - key: String::new(), + akey: String::new(), password_hash: Vec::new(), salt: crypto::get_random_64(), @@ -140,7 +140,7 @@ impl User { "MasterPasswordHint": self.password_hint, "Culture": "en-US", "TwoFactorEnabled": twofactor_enabled, - "Key": self.key, + "Key": self.akey, "PrivateKey": self.private_key, "SecurityStamp": self.security_stamp, "Organizations": orgs_json, @@ -163,7 +163,7 @@ impl User { pub fn delete(self, conn: &DbConn) -> EmptyResult { 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; if UserOrganization::find_by_org_and_type(&user_org.org_uuid, owner_type, &conn).len() <= 1 { err!("Can't delete last owner") diff --git a/src/db/schemas/mysql/schema.rs b/src/db/schemas/mysql/schema.rs new file mode 100644 index 0000000..51ec188 --- /dev/null +++ b/src/db/schemas/mysql/schema.rs @@ -0,0 +1,172 @@ +table! { + attachments (id) { + id -> Varchar, + cipher_uuid -> Varchar, + file_name -> Text, + file_size -> Integer, + akey -> Nullable, + } +} + +table! { + ciphers (uuid) { + uuid -> Varchar, + created_at -> Datetime, + updated_at -> Datetime, + user_uuid -> Nullable, + organization_uuid -> Nullable, + atype -> Integer, + name -> Text, + notes -> Nullable, + fields -> Nullable, + data -> Text, + favorite -> Bool, + password_history -> Nullable, + } +} + +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, + refresh_token -> Text, + twofactor_remember -> Nullable, + } +} + +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, + akey -> Text, + private_key -> Nullable, + public_key -> Nullable, + totp_secret -> Nullable, + totp_recover -> Nullable, + 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, +); diff --git a/src/db/schema.rs b/src/db/schemas/sqlite/schema.rs similarity index 92% rename from src/db/schema.rs rename to src/db/schemas/sqlite/schema.rs index 4816853..1bc924c 100644 --- a/src/db/schema.rs +++ b/src/db/schemas/sqlite/schema.rs @@ -4,7 +4,7 @@ table! { cipher_uuid -> Text, file_name -> Text, file_size -> Integer, - key -> Nullable, + akey -> Nullable, } } @@ -15,8 +15,7 @@ table! { updated_at -> Timestamp, user_uuid -> Nullable, organization_uuid -> Nullable, - #[sql_name = "type"] - type_ -> Integer, + atype -> Integer, name -> Text, notes -> Nullable, fields -> Nullable, @@ -48,8 +47,7 @@ table! { updated_at -> Timestamp, user_uuid -> Text, name -> Text, - #[sql_name = "type"] - type_ -> Integer, + atype -> Integer, push_token -> Nullable, refresh_token -> Text, twofactor_remember -> Nullable, @@ -91,8 +89,7 @@ table! { twofactor (uuid) { uuid -> Text, user_uuid -> Text, - #[sql_name = "type"] - type_ -> Integer, + atype -> Integer, enabled -> Bool, data -> Text, } @@ -109,7 +106,7 @@ table! { salt -> Binary, password_iterations -> Integer, password_hint -> Nullable, - key -> Text, + akey -> Text, private_key -> Nullable, public_key -> Nullable, totp_secret -> Nullable, @@ -136,10 +133,9 @@ table! { user_uuid -> Text, org_uuid -> Text, access_all -> Bool, - key -> Text, + akey -> Text, status -> Integer, - #[sql_name = "type"] - type_ -> Integer, + atype -> Integer, } } @@ -173,4 +169,4 @@ allow_tables_to_appear_in_same_query!( users, users_collections, users_organizations, -); +); \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 0d6c36e..4ff6c62 100644 --- a/src/main.rs +++ b/src/main.rs @@ -45,6 +45,12 @@ fn main() { init_logging().ok(); } + #[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"); + check_db(); check_rsa_keys(); check_web_vault(); @@ -123,24 +129,28 @@ fn chain_syslog(logger: fern::Dispatch) -> fern::Dispatch { fn check_db() { let url = CONFIG.database_url(); - let path = Path::new(&url); + if cfg!(feature = "sqlite") { + let path = Path::new(&url); - if let Some(parent) = path.parent() { - use std::fs; - if fs::create_dir_all(parent).is_err() { - error!("Error creating database directory"); - exit(1); + if let Some(parent) = path.parent() { + use std::fs; + if fs::create_dir_all(parent).is_err() { + error!("Error creating database directory"); + 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"); } } - - // 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"); - } + println!("{}", url.to_string()); + db::get_connection().expect("Can't connect to DB"); } fn check_rsa_keys() { @@ -207,7 +217,11 @@ fn check_web_vault() { // https://docs.rs/diesel_migrations/*/diesel_migrations/macro.embed_migrations.html #[allow(unused_imports)] mod migrations { - embed_migrations!(); + + #[cfg(feature = "sqlite")] + embed_migrations!("migrations/sqlite"); + #[cfg(feature = "mysql")] + embed_migrations!("migrations/mysql"); pub fn run_migrations() { // Make sure the database is up to date (create if it doesn't exist, or run the migrations)