mirror of
https://github.com/dani-garcia/vaultwarden.git
synced 2024-11-28 09:06:44 +03:00
Send API
This commit is contained in:
parent
2969e87b52
commit
8da5b99482
20 changed files with 812 additions and 2 deletions
|
@ -28,6 +28,7 @@
|
||||||
# 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
|
||||||
|
# SENDS_FOLDER=data/sends
|
||||||
|
|
||||||
## Templates data folder, by default uses embedded templates
|
## Templates data folder, by default uses embedded templates
|
||||||
## Check source code to see the format
|
## Check source code to see the format
|
||||||
|
|
1
migrations/mysql/2021-03-11-190243_add_sends/down.sql
Normal file
1
migrations/mysql/2021-03-11-190243_add_sends/down.sql
Normal file
|
@ -0,0 +1 @@
|
||||||
|
DROP TABLE sends;
|
25
migrations/mysql/2021-03-11-190243_add_sends/up.sql
Normal file
25
migrations/mysql/2021-03-11-190243_add_sends/up.sql
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
CREATE TABLE sends (
|
||||||
|
uuid CHAR(36) NOT NULL PRIMARY KEY,
|
||||||
|
user_uuid CHAR(36) REFERENCES users (uuid),
|
||||||
|
organization_uuid CHAR(36) REFERENCES organizations (uuid),
|
||||||
|
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
notes TEXT,
|
||||||
|
|
||||||
|
atype INTEGER NOT NULL,
|
||||||
|
data TEXT NOT NULL,
|
||||||
|
key TEXT NOT NULL,
|
||||||
|
password_hash BLOB,
|
||||||
|
password_salt BLOB,
|
||||||
|
password_iter INTEGER,
|
||||||
|
|
||||||
|
max_access_count INTEGER,
|
||||||
|
access_count INTEGER NOT NULL,
|
||||||
|
|
||||||
|
creation_date DATETIME NOT NULL,
|
||||||
|
revision_date DATETIME NOT NULL,
|
||||||
|
expiration_date DATETIME,
|
||||||
|
deletion_date DATETIME NOT NULL,
|
||||||
|
|
||||||
|
disabled BOOLEAN NOT NULL
|
||||||
|
);
|
|
@ -0,0 +1 @@
|
||||||
|
DROP TABLE sends;
|
25
migrations/postgresql/2021-03-11-190243_add_sends/up.sql
Normal file
25
migrations/postgresql/2021-03-11-190243_add_sends/up.sql
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
CREATE TABLE sends (
|
||||||
|
uuid CHAR(36) NOT NULL PRIMARY KEY,
|
||||||
|
user_uuid CHAR(36) REFERENCES users (uuid),
|
||||||
|
organization_uuid CHAR(36) REFERENCES organizations (uuid),
|
||||||
|
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
notes TEXT,
|
||||||
|
|
||||||
|
atype INTEGER NOT NULL,
|
||||||
|
data TEXT NOT NULL,
|
||||||
|
key TEXT NOT NULL,
|
||||||
|
password_hash BYTEA,
|
||||||
|
password_salt BYTEA,
|
||||||
|
password_iter INTEGER,
|
||||||
|
|
||||||
|
max_access_count INTEGER,
|
||||||
|
access_count INTEGER NOT NULL,
|
||||||
|
|
||||||
|
creation_date TIMESTAMP NOT NULL,
|
||||||
|
revision_date TIMESTAMP NOT NULL,
|
||||||
|
expiration_date TIMESTAMP,
|
||||||
|
deletion_date TIMESTAMP NOT NULL,
|
||||||
|
|
||||||
|
disabled BOOLEAN NOT NULL
|
||||||
|
);
|
1
migrations/sqlite/2021-03-11-190243_add_sends/down.sql
Normal file
1
migrations/sqlite/2021-03-11-190243_add_sends/down.sql
Normal file
|
@ -0,0 +1 @@
|
||||||
|
DROP TABLE sends;
|
25
migrations/sqlite/2021-03-11-190243_add_sends/up.sql
Normal file
25
migrations/sqlite/2021-03-11-190243_add_sends/up.sql
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
CREATE TABLE sends (
|
||||||
|
uuid TEXT NOT NULL PRIMARY KEY,
|
||||||
|
user_uuid TEXT REFERENCES users (uuid),
|
||||||
|
organization_uuid TEXT REFERENCES organizations (uuid),
|
||||||
|
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
notes TEXT,
|
||||||
|
|
||||||
|
atype INTEGER NOT NULL,
|
||||||
|
data TEXT NOT NULL,
|
||||||
|
key TEXT NOT NULL,
|
||||||
|
password_hash BLOB,
|
||||||
|
password_salt BLOB,
|
||||||
|
password_iter INTEGER,
|
||||||
|
|
||||||
|
max_access_count INTEGER,
|
||||||
|
access_count INTEGER NOT NULL,
|
||||||
|
|
||||||
|
creation_date DATETIME NOT NULL,
|
||||||
|
revision_date DATETIME NOT NULL,
|
||||||
|
expiration_date DATETIME,
|
||||||
|
deletion_date DATETIME NOT NULL,
|
||||||
|
|
||||||
|
disabled BOOLEAN NOT NULL
|
||||||
|
);
|
|
@ -104,6 +104,12 @@ fn sync(data: Form<SyncData>, headers: Headers, conn: DbConn) -> JsonResult {
|
||||||
.map(|c| c.to_json(&headers.host, &headers.user.uuid, &conn))
|
.map(|c| c.to_json(&headers.host, &headers.user.uuid, &conn))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
let sends = Send::find_by_user(&headers.user.uuid, &conn);
|
||||||
|
let sends_json: Vec<Value> = sends
|
||||||
|
.iter()
|
||||||
|
.map(|s| s.to_json())
|
||||||
|
.collect();
|
||||||
|
|
||||||
let domains_json = if data.exclude_domains {
|
let domains_json = if data.exclude_domains {
|
||||||
Value::Null
|
Value::Null
|
||||||
} else {
|
} else {
|
||||||
|
@ -117,6 +123,7 @@ fn sync(data: Form<SyncData>, headers: Headers, conn: DbConn) -> JsonResult {
|
||||||
"Policies": policies_json,
|
"Policies": policies_json,
|
||||||
"Ciphers": ciphers_json,
|
"Ciphers": ciphers_json,
|
||||||
"Domains": domains_json,
|
"Domains": domains_json,
|
||||||
|
"Sends": sends_json,
|
||||||
"Object": "sync"
|
"Object": "sync"
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ mod ciphers;
|
||||||
mod folders;
|
mod folders;
|
||||||
mod organizations;
|
mod organizations;
|
||||||
pub mod two_factor;
|
pub mod two_factor;
|
||||||
|
mod sends;
|
||||||
|
|
||||||
pub fn routes() -> Vec<Route> {
|
pub fn routes() -> Vec<Route> {
|
||||||
let mut mod_routes = routes![
|
let mut mod_routes = routes![
|
||||||
|
@ -20,6 +21,7 @@ pub fn routes() -> Vec<Route> {
|
||||||
routes.append(&mut folders::routes());
|
routes.append(&mut folders::routes());
|
||||||
routes.append(&mut organizations::routes());
|
routes.append(&mut organizations::routes());
|
||||||
routes.append(&mut two_factor::routes());
|
routes.append(&mut two_factor::routes());
|
||||||
|
routes.append(&mut sends::routes());
|
||||||
routes.append(&mut mod_routes);
|
routes.append(&mut mod_routes);
|
||||||
|
|
||||||
routes
|
routes
|
||||||
|
|
383
src/api/core/sends.rs
Normal file
383
src/api/core/sends.rs
Normal file
|
@ -0,0 +1,383 @@
|
||||||
|
use std::{io::Read, path::Path};
|
||||||
|
|
||||||
|
use chrono::{DateTime, Duration, Utc};
|
||||||
|
use multipart::server::{save::SavedData, Multipart, SaveResult};
|
||||||
|
use rocket::{http::ContentType, Data};
|
||||||
|
use rocket_contrib::json::Json;
|
||||||
|
use serde_json::Value;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
api::{ApiResult, EmptyResult, JsonResult, JsonUpcase, Notify, UpdateType},
|
||||||
|
auth::{Headers, Host},
|
||||||
|
db::{models::*, DbConn},
|
||||||
|
CONFIG,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn routes() -> Vec<rocket::Route> {
|
||||||
|
routes![
|
||||||
|
post_send,
|
||||||
|
post_send_file,
|
||||||
|
post_access,
|
||||||
|
post_access_file,
|
||||||
|
put_send,
|
||||||
|
delete_send,
|
||||||
|
put_remove_password
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
pub struct SendData {
|
||||||
|
pub Type: i32,
|
||||||
|
pub Key: String,
|
||||||
|
pub Password: Option<String>,
|
||||||
|
pub MaxAccessCount: Option<i32>,
|
||||||
|
pub ExpirationDate: Option<DateTime<Utc>>,
|
||||||
|
pub DeletionDate: DateTime<Utc>,
|
||||||
|
pub Disabled: bool,
|
||||||
|
|
||||||
|
// Data field
|
||||||
|
pub Name: String,
|
||||||
|
pub Notes: Option<String>,
|
||||||
|
pub Text: Option<Value>,
|
||||||
|
pub File: Option<Value>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_send(data: SendData, user_uuid: String) -> ApiResult<Send> {
|
||||||
|
let data_val = if data.Type == SendType::Text as i32 {
|
||||||
|
data.Text
|
||||||
|
} else if data.Type == SendType::File as i32 {
|
||||||
|
data.File
|
||||||
|
} else {
|
||||||
|
err!("Invalid Send type")
|
||||||
|
};
|
||||||
|
|
||||||
|
let data_str = if let Some(mut d) = data_val {
|
||||||
|
d.as_object_mut().and_then(|o| o.remove("Response"));
|
||||||
|
serde_json::to_string(&d)?
|
||||||
|
} else {
|
||||||
|
err!("Send data not provided");
|
||||||
|
};
|
||||||
|
|
||||||
|
if data.DeletionDate > Utc::now() + Duration::days(31) {
|
||||||
|
err!(
|
||||||
|
"You cannot have a Send with a deletion date that far into the future. Adjust the Deletion Date to a value less than 31 days from now and try again."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut send = Send::new(data.Type, data.Name, data_str, data.Key, data.DeletionDate.naive_utc());
|
||||||
|
send.user_uuid = Some(user_uuid);
|
||||||
|
send.notes = data.Notes;
|
||||||
|
send.max_access_count = data.MaxAccessCount;
|
||||||
|
send.expiration_date = data.ExpirationDate.map(|d| d.naive_utc());
|
||||||
|
send.disabled = data.Disabled;
|
||||||
|
send.atype = data.Type;
|
||||||
|
|
||||||
|
send.set_password(data.Password.as_deref());
|
||||||
|
|
||||||
|
Ok(send)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/sends", data = "<data>")]
|
||||||
|
fn post_send(data: JsonUpcase<SendData>, headers: Headers, conn: DbConn, nt: Notify) -> JsonResult {
|
||||||
|
let data: SendData = data.into_inner().data;
|
||||||
|
|
||||||
|
if data.Type == SendType::File as i32 {
|
||||||
|
err!("File sends should use /api/sends/file")
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut send = create_send(data, headers.user.uuid.clone())?;
|
||||||
|
send.save(&conn)?;
|
||||||
|
nt.send_user_update(UpdateType::SyncSendCreate, &headers.user);
|
||||||
|
|
||||||
|
Ok(Json(send.to_json()))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/sends/file", format = "multipart/form-data", data = "<data>")]
|
||||||
|
fn post_send_file(data: Data, content_type: &ContentType, headers: Headers, conn: DbConn, nt: Notify) -> JsonResult {
|
||||||
|
let boundary = content_type.params().next().expect("No boundary provided").1;
|
||||||
|
|
||||||
|
let mut mpart = Multipart::with_body(data.open(), boundary);
|
||||||
|
|
||||||
|
// First entry is the SendData JSON
|
||||||
|
let mut model_entry = match mpart.read_entry()? {
|
||||||
|
Some(e) if &*e.headers.name == "model" => e,
|
||||||
|
Some(_) => err!("Invalid entry name"),
|
||||||
|
None => err!("No model entry present"),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut buf = String::new();
|
||||||
|
model_entry.data.read_to_string(&mut buf)?;
|
||||||
|
let data = serde_json::from_str::<crate::util::UpCase<SendData>>(&buf)?;
|
||||||
|
|
||||||
|
// Get the file length and add an extra 10% to avoid issues
|
||||||
|
const SIZE_110_MB: u64 = 115_343_360;
|
||||||
|
|
||||||
|
let size_limit = match CONFIG.user_attachment_limit() {
|
||||||
|
Some(0) => err!("File uploads are disabled"),
|
||||||
|
Some(limit_kb) => {
|
||||||
|
let left = (limit_kb * 1024) - Attachment::size_by_user(&headers.user.uuid, &conn);
|
||||||
|
if left <= 0 {
|
||||||
|
err!("Attachment size limit reached! Delete some files to open space")
|
||||||
|
}
|
||||||
|
std::cmp::Ord::max(left as u64, SIZE_110_MB)
|
||||||
|
}
|
||||||
|
None => SIZE_110_MB,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create the Send
|
||||||
|
let mut send = create_send(data.data, headers.user.uuid.clone())?;
|
||||||
|
let file_id: String = data_encoding::HEXLOWER.encode(&crate::crypto::get_random(vec![0; 32]));
|
||||||
|
|
||||||
|
if send.atype != SendType::File as i32 {
|
||||||
|
err!("Send content is not a file");
|
||||||
|
}
|
||||||
|
|
||||||
|
let file_path = Path::new(&CONFIG.sends_folder()).join(&send.uuid).join(&file_id);
|
||||||
|
|
||||||
|
// Read the data entry and save the file
|
||||||
|
let mut data_entry = match mpart.read_entry()? {
|
||||||
|
Some(e) if &*e.headers.name == "data" => e,
|
||||||
|
Some(_) => err!("Invalid entry name"),
|
||||||
|
None => err!("No model entry present"),
|
||||||
|
};
|
||||||
|
|
||||||
|
let size = match data_entry
|
||||||
|
.data
|
||||||
|
.save()
|
||||||
|
.memory_threshold(0)
|
||||||
|
.size_limit(size_limit)
|
||||||
|
.with_path(&file_path)
|
||||||
|
{
|
||||||
|
SaveResult::Full(SavedData::File(_, size)) => size as i32,
|
||||||
|
SaveResult::Full(other) => {
|
||||||
|
std::fs::remove_file(&file_path).ok();
|
||||||
|
err!(format!("Attachment is not a file: {:?}", other));
|
||||||
|
}
|
||||||
|
SaveResult::Partial(_, reason) => {
|
||||||
|
std::fs::remove_file(&file_path).ok();
|
||||||
|
err!(format!("Attachment size limit exceeded with this file: {:?}", reason));
|
||||||
|
}
|
||||||
|
SaveResult::Error(e) => {
|
||||||
|
std::fs::remove_file(&file_path).ok();
|
||||||
|
err!(format!("Error: {:?}", e));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Set ID and sizes
|
||||||
|
let mut data_value: Value = serde_json::from_str(&send.data)?;
|
||||||
|
if let Some(o) = data_value.as_object_mut() {
|
||||||
|
o.insert(String::from("Id"), Value::String(file_id));
|
||||||
|
o.insert(String::from("Size"), Value::Number(size.into()));
|
||||||
|
o.insert(
|
||||||
|
String::from("SizeName"),
|
||||||
|
Value::String(crate::util::get_display_size(size)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
send.data = serde_json::to_string(&data_value)?;
|
||||||
|
|
||||||
|
// Save the changes in the database
|
||||||
|
send.save(&conn)?;
|
||||||
|
nt.send_user_update(UpdateType::SyncSendCreate, &headers.user);
|
||||||
|
|
||||||
|
Ok(Json(send.to_json()))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
pub struct SendAccessData {
|
||||||
|
pub Password: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/sends/access/<access_id>", data = "<data>")]
|
||||||
|
fn post_access(access_id: String, data: JsonUpcase<SendAccessData>, conn: DbConn) -> JsonResult {
|
||||||
|
let mut send = match Send::find_by_access_id(&access_id, &conn) {
|
||||||
|
Some(s) => s,
|
||||||
|
None => err_code!("Send not found", 404),
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(max_access_count) = send.max_access_count {
|
||||||
|
if send.access_count > max_access_count {
|
||||||
|
err_code!("Max access count reached", 404);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(expiration) = send.expiration_date {
|
||||||
|
if Utc::now().naive_utc() > expiration {
|
||||||
|
err_code!("Send has expired", 404)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if Utc::now().naive_utc() > send.deletion_date {
|
||||||
|
err_code!("Send has been deleted", 404)
|
||||||
|
}
|
||||||
|
|
||||||
|
if send.disabled {
|
||||||
|
err_code!("Send has been disabled", 404)
|
||||||
|
}
|
||||||
|
|
||||||
|
if send.password_hash.is_some() {
|
||||||
|
match data.into_inner().data.Password {
|
||||||
|
Some(ref p) if send.check_password(p) => { /* Nothing to do here */ }
|
||||||
|
Some(_) => err!("Invalid password."),
|
||||||
|
None => err_code!("Password not provided", 401),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Files are incremented during the download
|
||||||
|
if send.atype == SendType::Text as i32 {
|
||||||
|
send.access_count += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
send.save(&conn)?;
|
||||||
|
|
||||||
|
Ok(Json(send.to_json()))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/sends/<send_id>/access/file/<file_id>", data = "<data>")]
|
||||||
|
fn post_access_file(
|
||||||
|
send_id: String,
|
||||||
|
file_id: String,
|
||||||
|
data: JsonUpcase<SendAccessData>,
|
||||||
|
host: Host,
|
||||||
|
conn: DbConn,
|
||||||
|
) -> JsonResult {
|
||||||
|
let mut send = match Send::find_by_uuid(&send_id, &conn) {
|
||||||
|
Some(s) => s,
|
||||||
|
None => err_code!("Send not found", 404),
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(max_access_count) = send.max_access_count {
|
||||||
|
if send.access_count > max_access_count {
|
||||||
|
err_code!("Max access count reached", 404);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(expiration) = send.expiration_date {
|
||||||
|
if Utc::now().naive_utc() > expiration {
|
||||||
|
err_code!("Send has expired", 404)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if Utc::now().naive_utc() > send.deletion_date {
|
||||||
|
err_code!("Send has been deleted", 404)
|
||||||
|
}
|
||||||
|
|
||||||
|
if send.disabled {
|
||||||
|
err_code!("Send has been disabled", 404)
|
||||||
|
}
|
||||||
|
|
||||||
|
if send.password_hash.is_some() {
|
||||||
|
match data.into_inner().data.Password {
|
||||||
|
Some(ref p) if send.check_password(p) => { /* Nothing to do here */ }
|
||||||
|
Some(_) => err!("Invalid password."),
|
||||||
|
None => err_code!("Password not provided", 401),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
send.access_count += 1;
|
||||||
|
|
||||||
|
send.save(&conn)?;
|
||||||
|
|
||||||
|
Ok(Json(json!({
|
||||||
|
"Object": "send-fileDownload",
|
||||||
|
"Id": file_id,
|
||||||
|
"Url": format!("{}/sends/{}/{}", &host.host, send_id, file_id)
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[put("/sends/<id>", data = "<data>")]
|
||||||
|
fn put_send(id: String, data: JsonUpcase<SendData>, headers: Headers, conn: DbConn, nt: Notify) -> JsonResult {
|
||||||
|
let data: SendData = data.into_inner().data;
|
||||||
|
|
||||||
|
let mut send = match Send::find_by_uuid(&id, &conn) {
|
||||||
|
Some(s) => s,
|
||||||
|
None => err!("Send not found"),
|
||||||
|
};
|
||||||
|
|
||||||
|
if send.user_uuid.as_ref() != Some(&headers.user.uuid) {
|
||||||
|
err!("Send is not owned by user")
|
||||||
|
}
|
||||||
|
|
||||||
|
if send.atype != data.Type {
|
||||||
|
err!("Sends can't change type")
|
||||||
|
}
|
||||||
|
|
||||||
|
let data_val = if data.Type == SendType::Text as i32 {
|
||||||
|
data.Text
|
||||||
|
} else if data.Type == SendType::File as i32 {
|
||||||
|
data.File
|
||||||
|
} else {
|
||||||
|
err!("Invalid Send type")
|
||||||
|
};
|
||||||
|
|
||||||
|
let data_str = if let Some(mut d) = data_val {
|
||||||
|
d.as_object_mut().and_then(|d| d.remove("Response"));
|
||||||
|
serde_json::to_string(&d)?
|
||||||
|
} else {
|
||||||
|
err!("Send data not provided");
|
||||||
|
};
|
||||||
|
|
||||||
|
if data.DeletionDate > Utc::now() + Duration::days(31) {
|
||||||
|
err!(
|
||||||
|
"You cannot have a Send with a deletion date that far into the future. Adjust the Deletion Date to a value less than 31 days from now and try again."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
send.data = data_str;
|
||||||
|
send.name = data.Name;
|
||||||
|
send.key = data.Key;
|
||||||
|
send.deletion_date = data.DeletionDate.naive_utc();
|
||||||
|
send.notes = data.Notes;
|
||||||
|
send.max_access_count = data.MaxAccessCount;
|
||||||
|
send.expiration_date = data.ExpirationDate.map(|d| d.naive_utc());
|
||||||
|
send.disabled = data.Disabled;
|
||||||
|
|
||||||
|
// Only change the value if it's present
|
||||||
|
if let Some(password) = data.Password {
|
||||||
|
send.set_password(Some(&password));
|
||||||
|
}
|
||||||
|
|
||||||
|
send.save(&conn)?;
|
||||||
|
nt.send_user_update(UpdateType::SyncSendUpdate, &headers.user);
|
||||||
|
|
||||||
|
Ok(Json(send.to_json()))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[delete("/sends/<id>")]
|
||||||
|
fn delete_send(id: String, headers: Headers, conn: DbConn, nt: Notify) -> EmptyResult {
|
||||||
|
let send = match Send::find_by_uuid(&id, &conn) {
|
||||||
|
Some(s) => s,
|
||||||
|
None => err!("Send not found"),
|
||||||
|
};
|
||||||
|
|
||||||
|
if send.user_uuid.as_ref() != Some(&headers.user.uuid) {
|
||||||
|
err!("Send is not owned by user")
|
||||||
|
}
|
||||||
|
|
||||||
|
if send.atype == SendType::File as i32 {
|
||||||
|
std::fs::remove_dir_all(Path::new(&CONFIG.sends_folder()).join(&send.uuid)).ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
send.delete(&conn)?;
|
||||||
|
nt.send_user_update(UpdateType::SyncSendDelete, &headers.user);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[put("/sends/<id>/remove-password")]
|
||||||
|
fn put_remove_password(id: String, headers: Headers, conn: DbConn, nt: Notify) -> JsonResult {
|
||||||
|
let mut send = match Send::find_by_uuid(&id, &conn) {
|
||||||
|
Some(s) => s,
|
||||||
|
None => err!("Send not found"),
|
||||||
|
};
|
||||||
|
|
||||||
|
if send.user_uuid.as_ref() != Some(&headers.user.uuid) {
|
||||||
|
err!("Send is not owned by user")
|
||||||
|
}
|
||||||
|
|
||||||
|
send.set_password(None);
|
||||||
|
send.save(&conn)?;
|
||||||
|
nt.send_user_update(UpdateType::SyncSendUpdate, &headers.user);
|
||||||
|
|
||||||
|
Ok(Json(send.to_json()))
|
||||||
|
}
|
|
@ -394,6 +394,10 @@ pub enum UpdateType {
|
||||||
|
|
||||||
LogOut = 11,
|
LogOut = 11,
|
||||||
|
|
||||||
|
SyncSendCreate = 12,
|
||||||
|
SyncSendUpdate = 13,
|
||||||
|
SyncSendDelete = 14,
|
||||||
|
|
||||||
None = 100,
|
None = 100,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ pub fn routes() -> Vec<Route> {
|
||||||
// If addding more routes here, consider also adding them to
|
// If addding more routes here, consider also adding them to
|
||||||
// crate::utils::LOGGED_ROUTES to make sure they appear in the log
|
// crate::utils::LOGGED_ROUTES to make sure they appear in the log
|
||||||
if CONFIG.web_vault_enabled() {
|
if CONFIG.web_vault_enabled() {
|
||||||
routes![web_index, app_id, web_files, attachments, alive, static_files]
|
routes![web_index, app_id, web_files, attachments, sends, alive, static_files]
|
||||||
} else {
|
} else {
|
||||||
routes![attachments, alive, static_files]
|
routes![attachments, alive, static_files]
|
||||||
}
|
}
|
||||||
|
@ -60,6 +60,11 @@ fn attachments(uuid: String, file: PathBuf) -> Option<NamedFile> {
|
||||||
NamedFile::open(Path::new(&CONFIG.attachments_folder()).join(uuid).join(file)).ok()
|
NamedFile::open(Path::new(&CONFIG.attachments_folder()).join(uuid).join(file)).ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[get("/sends/<send_id>/<file_id>")]
|
||||||
|
fn sends(send_id: String, file_id: String) -> Option<NamedFile> {
|
||||||
|
NamedFile::open(Path::new(&CONFIG.sends_folder()).join(send_id).join(file_id)).ok()
|
||||||
|
}
|
||||||
|
|
||||||
#[get("/alive")]
|
#[get("/alive")]
|
||||||
fn alive() -> Json<String> {
|
fn alive() -> Json<String> {
|
||||||
use crate::util::format_date;
|
use crate::util::format_date;
|
||||||
|
|
|
@ -299,6 +299,8 @@ make_config! {
|
||||||
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");
|
||||||
|
/// Sends folder
|
||||||
|
sends_folder: String, false, auto, |c| format!("{}/{}", c.data_folder, "sends");
|
||||||
/// Templates folder
|
/// Templates folder
|
||||||
templates_folder: String, false, auto, |c| format!("{}/{}", c.data_folder, "templates");
|
templates_folder: String, false, auto, |c| format!("{}/{}", c.data_folder, "templates");
|
||||||
/// Session JWT key
|
/// Session JWT key
|
||||||
|
|
|
@ -8,6 +8,7 @@ mod org_policy;
|
||||||
mod organization;
|
mod organization;
|
||||||
mod two_factor;
|
mod two_factor;
|
||||||
mod user;
|
mod user;
|
||||||
|
mod send;
|
||||||
|
|
||||||
pub use self::attachment::Attachment;
|
pub use self::attachment::Attachment;
|
||||||
pub use self::cipher::Cipher;
|
pub use self::cipher::Cipher;
|
||||||
|
@ -19,3 +20,4 @@ pub use self::org_policy::{OrgPolicy, OrgPolicyType};
|
||||||
pub use self::organization::{Organization, UserOrgStatus, UserOrgType, UserOrganization};
|
pub use self::organization::{Organization, UserOrgStatus, UserOrgType, UserOrganization};
|
||||||
pub use self::two_factor::{TwoFactor, TwoFactorType};
|
pub use self::two_factor::{TwoFactor, TwoFactorType};
|
||||||
pub use self::user::{Invitation, User, UserStampException};
|
pub use self::user::{Invitation, User, UserStampException};
|
||||||
|
pub use self::send::{Send, SendType};
|
235
src/db/models/send.rs
Normal file
235
src/db/models/send.rs
Normal file
|
@ -0,0 +1,235 @@
|
||||||
|
use chrono::{NaiveDateTime, Utc};
|
||||||
|
use serde_json::Value;
|
||||||
|
|
||||||
|
use super::{Organization, User};
|
||||||
|
|
||||||
|
db_object! {
|
||||||
|
#[derive(Identifiable, Queryable, Insertable, Associations, AsChangeset)]
|
||||||
|
#[table_name = "sends"]
|
||||||
|
#[changeset_options(treat_none_as_null="true")]
|
||||||
|
#[belongs_to(User, foreign_key = "user_uuid")]
|
||||||
|
#[belongs_to(Organization, foreign_key = "organization_uuid")]
|
||||||
|
#[primary_key(uuid)]
|
||||||
|
pub struct Send {
|
||||||
|
pub uuid: String,
|
||||||
|
|
||||||
|
pub user_uuid: Option<String>,
|
||||||
|
pub organization_uuid: Option<String>,
|
||||||
|
|
||||||
|
|
||||||
|
pub name: String,
|
||||||
|
pub notes: Option<String>,
|
||||||
|
|
||||||
|
pub atype: i32,
|
||||||
|
pub data: String,
|
||||||
|
pub key: String,
|
||||||
|
pub password_hash: Option<Vec<u8>>,
|
||||||
|
password_salt: Option<Vec<u8>>,
|
||||||
|
password_iter: Option<i32>,
|
||||||
|
|
||||||
|
pub max_access_count: Option<i32>,
|
||||||
|
pub access_count: i32,
|
||||||
|
|
||||||
|
pub creation_date: NaiveDateTime,
|
||||||
|
pub revision_date: NaiveDateTime,
|
||||||
|
pub expiration_date: Option<NaiveDateTime>,
|
||||||
|
pub deletion_date: NaiveDateTime,
|
||||||
|
|
||||||
|
pub disabled: bool,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, PartialEq, Eq, num_derive::FromPrimitive)]
|
||||||
|
pub enum SendType {
|
||||||
|
Text = 0,
|
||||||
|
File = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Send {
|
||||||
|
pub fn new(atype: i32, name: String, data: String, key: String, deletion_date: NaiveDateTime) -> Self {
|
||||||
|
let now = Utc::now().naive_utc();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
uuid: crate::util::get_uuid(),
|
||||||
|
user_uuid: None,
|
||||||
|
organization_uuid: None,
|
||||||
|
|
||||||
|
name,
|
||||||
|
notes: None,
|
||||||
|
|
||||||
|
atype,
|
||||||
|
data,
|
||||||
|
key,
|
||||||
|
password_hash: None,
|
||||||
|
password_salt: None,
|
||||||
|
password_iter: None,
|
||||||
|
|
||||||
|
max_access_count: None,
|
||||||
|
access_count: 0,
|
||||||
|
|
||||||
|
creation_date: now,
|
||||||
|
revision_date: now,
|
||||||
|
expiration_date: None,
|
||||||
|
deletion_date,
|
||||||
|
|
||||||
|
disabled: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_password(&mut self, password: Option<&str>) {
|
||||||
|
const PASSWORD_ITER: i32 = 100_000;
|
||||||
|
|
||||||
|
if let Some(password) = password {
|
||||||
|
self.password_iter = Some(PASSWORD_ITER);
|
||||||
|
let salt = crate::crypto::get_random_64();
|
||||||
|
let hash = crate::crypto::hash_password(password.as_bytes(), &salt, PASSWORD_ITER as u32);
|
||||||
|
self.password_salt = Some(salt);
|
||||||
|
self.password_hash = Some(hash);
|
||||||
|
} else {
|
||||||
|
self.password_iter = None;
|
||||||
|
self.password_salt = None;
|
||||||
|
self.password_hash = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn check_password(&self, password: &str) -> bool {
|
||||||
|
match (&self.password_hash, &self.password_salt, self.password_iter) {
|
||||||
|
(Some(hash), Some(salt), Some(iter)) => {
|
||||||
|
crate::crypto::verify_password_hash(password.as_bytes(), salt, hash, iter as u32)
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_json(&self) -> Value {
|
||||||
|
use crate::util::format_date;
|
||||||
|
use data_encoding::BASE64URL_NOPAD;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
let data: Value = serde_json::from_str(&self.data).unwrap_or_default();
|
||||||
|
|
||||||
|
json!({
|
||||||
|
"Id": self.uuid,
|
||||||
|
"AccessId": BASE64URL_NOPAD.encode(Uuid::parse_str(&self.uuid).unwrap_or_default().as_bytes()),
|
||||||
|
"Type": self.atype,
|
||||||
|
|
||||||
|
"Name": self.name,
|
||||||
|
"Notes": self.notes,
|
||||||
|
"Text": if self.atype == SendType::Text as i32 { Some(&data) } else { None },
|
||||||
|
"File": if self.atype == SendType::File as i32 { Some(&data) } else { None },
|
||||||
|
|
||||||
|
"Key": self.key,
|
||||||
|
"MaxAccessCount": self.max_access_count,
|
||||||
|
"AccessCount": self.access_count,
|
||||||
|
"Password": self.password_hash.as_deref().map(|h| BASE64URL_NOPAD.encode(h)),
|
||||||
|
"Disabled": self.disabled,
|
||||||
|
|
||||||
|
"RevisionDate": format_date(&self.revision_date),
|
||||||
|
"ExpirationDate": self.expiration_date.as_ref().map(format_date),
|
||||||
|
"DeletionDate": format_date(&self.deletion_date),
|
||||||
|
"Object": "send",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
use crate::db::DbConn;
|
||||||
|
|
||||||
|
use crate::api::EmptyResult;
|
||||||
|
use crate::error::MapResult;
|
||||||
|
|
||||||
|
impl Send {
|
||||||
|
pub fn save(&mut self, conn: &DbConn) -> EmptyResult {
|
||||||
|
// self.update_users_revision(conn);
|
||||||
|
self.revision_date = Utc::now().naive_utc();
|
||||||
|
|
||||||
|
db_run! { conn:
|
||||||
|
sqlite, mysql {
|
||||||
|
match diesel::replace_into(sends::table)
|
||||||
|
.values(SendDb::to_db(self))
|
||||||
|
.execute(conn)
|
||||||
|
{
|
||||||
|
Ok(_) => Ok(()),
|
||||||
|
// Record already exists and causes a Foreign Key Violation because replace_into() wants to delete the record first.
|
||||||
|
Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => {
|
||||||
|
diesel::update(sends::table)
|
||||||
|
.filter(sends::uuid.eq(&self.uuid))
|
||||||
|
.set(SendDb::to_db(self))
|
||||||
|
.execute(conn)
|
||||||
|
.map_res("Error saving send")
|
||||||
|
}
|
||||||
|
Err(e) => Err(e.into()),
|
||||||
|
}.map_res("Error saving send")
|
||||||
|
}
|
||||||
|
postgresql {
|
||||||
|
let value = SendDb::to_db(self);
|
||||||
|
diesel::insert_into(sends::table)
|
||||||
|
.values(&value)
|
||||||
|
.on_conflict(sends::uuid)
|
||||||
|
.do_update()
|
||||||
|
.set(&value)
|
||||||
|
.execute(conn)
|
||||||
|
.map_res("Error saving send")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn delete(&self, conn: &DbConn) -> EmptyResult {
|
||||||
|
// self.update_users_revision(conn);
|
||||||
|
|
||||||
|
db_run! { conn: {
|
||||||
|
diesel::delete(sends::table.filter(sends::uuid.eq(&self.uuid)))
|
||||||
|
.execute(conn)
|
||||||
|
.map_res("Error deleting send")
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn delete_all_by_user(user_uuid: &str, conn: &DbConn) -> EmptyResult {
|
||||||
|
for send in Self::find_by_user(user_uuid, &conn) {
|
||||||
|
send.delete(&conn)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn find_by_access_id(access_id: &str, conn: &DbConn) -> Option<Self> {
|
||||||
|
use data_encoding::BASE64URL_NOPAD;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
let uuid_vec = match BASE64URL_NOPAD.decode(access_id.as_bytes()) {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(_) => return None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let uuid = match Uuid::from_slice(&uuid_vec) {
|
||||||
|
Ok(u) => u.to_string(),
|
||||||
|
Err(_) => return None,
|
||||||
|
};
|
||||||
|
|
||||||
|
Self::find_by_uuid(&uuid, conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn find_by_uuid(uuid: &str, conn: &DbConn) -> Option<Self> {
|
||||||
|
db_run! {conn: {
|
||||||
|
sends::table
|
||||||
|
.filter(sends::uuid.eq(uuid))
|
||||||
|
.first::<SendDb>(conn)
|
||||||
|
.ok()
|
||||||
|
.from_db()
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn find_by_user(user_uuid: &str, conn: &DbConn) -> Vec<Self> {
|
||||||
|
db_run! {conn: {
|
||||||
|
sends::table
|
||||||
|
.filter(sends::user_uuid.eq(user_uuid))
|
||||||
|
.load::<SendDb>(conn).expect("Error loading sends").from_db()
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn find_by_org(org_uuid: &str, conn: &DbConn) -> Vec<Self> {
|
||||||
|
db_run! {conn: {
|
||||||
|
sends::table
|
||||||
|
.filter(sends::organization_uuid.eq(org_uuid))
|
||||||
|
.load::<SendDb>(conn).expect("Error loading sends").from_db()
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
}
|
|
@ -177,7 +177,7 @@ impl User {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
use super::{Cipher, Device, Favorite, Folder, TwoFactor, UserOrgType, UserOrganization};
|
use super::{Cipher, Device, Favorite, Folder, Send, TwoFactor, UserOrgType, UserOrganization};
|
||||||
use crate::db::DbConn;
|
use crate::db::DbConn;
|
||||||
|
|
||||||
use crate::api::EmptyResult;
|
use crate::api::EmptyResult;
|
||||||
|
@ -263,6 +263,7 @@ impl User {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Send::delete_all_by_user(&self.uuid, conn)?;
|
||||||
UserOrganization::delete_all_by_user(&self.uuid, conn)?;
|
UserOrganization::delete_all_by_user(&self.uuid, conn)?;
|
||||||
Cipher::delete_all_by_user(&self.uuid, conn)?;
|
Cipher::delete_all_by_user(&self.uuid, conn)?;
|
||||||
Favorite::delete_all_by_user(&self.uuid, conn)?;
|
Favorite::delete_all_by_user(&self.uuid, conn)?;
|
||||||
|
|
|
@ -102,6 +102,29 @@ table! {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
table! {
|
||||||
|
sends (uuid) {
|
||||||
|
uuid -> Text,
|
||||||
|
user_uuid -> Nullable<Text>,
|
||||||
|
organization_uuid -> Nullable<Text>,
|
||||||
|
name -> Text,
|
||||||
|
notes -> Nullable<Text>,
|
||||||
|
atype -> Integer,
|
||||||
|
data -> Text,
|
||||||
|
key -> Text,
|
||||||
|
password_hash -> Nullable<Binary>,
|
||||||
|
password_salt -> Nullable<Binary>,
|
||||||
|
password_iter -> Nullable<Integer>,
|
||||||
|
max_access_count -> Nullable<Integer>,
|
||||||
|
access_count -> Integer,
|
||||||
|
creation_date -> Datetime,
|
||||||
|
revision_date -> Datetime,
|
||||||
|
expiration_date -> Nullable<Datetime>,
|
||||||
|
deletion_date -> Datetime,
|
||||||
|
disabled -> Bool,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
table! {
|
table! {
|
||||||
twofactor (uuid) {
|
twofactor (uuid) {
|
||||||
uuid -> Text,
|
uuid -> Text,
|
||||||
|
@ -176,6 +199,8 @@ joinable!(folders -> users (user_uuid));
|
||||||
joinable!(folders_ciphers -> ciphers (cipher_uuid));
|
joinable!(folders_ciphers -> ciphers (cipher_uuid));
|
||||||
joinable!(folders_ciphers -> folders (folder_uuid));
|
joinable!(folders_ciphers -> folders (folder_uuid));
|
||||||
joinable!(org_policies -> organizations (org_uuid));
|
joinable!(org_policies -> organizations (org_uuid));
|
||||||
|
joinable!(sends -> organizations (organization_uuid));
|
||||||
|
joinable!(sends -> users (user_uuid));
|
||||||
joinable!(twofactor -> users (user_uuid));
|
joinable!(twofactor -> users (user_uuid));
|
||||||
joinable!(users_collections -> collections (collection_uuid));
|
joinable!(users_collections -> collections (collection_uuid));
|
||||||
joinable!(users_collections -> users (user_uuid));
|
joinable!(users_collections -> users (user_uuid));
|
||||||
|
@ -193,6 +218,7 @@ allow_tables_to_appear_in_same_query!(
|
||||||
invitations,
|
invitations,
|
||||||
org_policies,
|
org_policies,
|
||||||
organizations,
|
organizations,
|
||||||
|
sends,
|
||||||
twofactor,
|
twofactor,
|
||||||
users,
|
users,
|
||||||
users_collections,
|
users_collections,
|
||||||
|
|
|
@ -102,6 +102,29 @@ table! {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
table! {
|
||||||
|
sends (uuid) {
|
||||||
|
uuid -> Text,
|
||||||
|
user_uuid -> Nullable<Text>,
|
||||||
|
organization_uuid -> Nullable<Text>,
|
||||||
|
name -> Text,
|
||||||
|
notes -> Nullable<Text>,
|
||||||
|
atype -> Integer,
|
||||||
|
data -> Text,
|
||||||
|
key -> Text,
|
||||||
|
password_hash -> Nullable<Binary>,
|
||||||
|
password_salt -> Nullable<Binary>,
|
||||||
|
password_iter -> Nullable<Integer>,
|
||||||
|
max_access_count -> Nullable<Integer>,
|
||||||
|
access_count -> Integer,
|
||||||
|
creation_date -> Timestamp,
|
||||||
|
revision_date -> Timestamp,
|
||||||
|
expiration_date -> Nullable<Timestamp>,
|
||||||
|
deletion_date -> Timestamp,
|
||||||
|
disabled -> Bool,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
table! {
|
table! {
|
||||||
twofactor (uuid) {
|
twofactor (uuid) {
|
||||||
uuid -> Text,
|
uuid -> Text,
|
||||||
|
@ -176,6 +199,8 @@ joinable!(folders -> users (user_uuid));
|
||||||
joinable!(folders_ciphers -> ciphers (cipher_uuid));
|
joinable!(folders_ciphers -> ciphers (cipher_uuid));
|
||||||
joinable!(folders_ciphers -> folders (folder_uuid));
|
joinable!(folders_ciphers -> folders (folder_uuid));
|
||||||
joinable!(org_policies -> organizations (org_uuid));
|
joinable!(org_policies -> organizations (org_uuid));
|
||||||
|
joinable!(sends -> organizations (organization_uuid));
|
||||||
|
joinable!(sends -> users (user_uuid));
|
||||||
joinable!(twofactor -> users (user_uuid));
|
joinable!(twofactor -> users (user_uuid));
|
||||||
joinable!(users_collections -> collections (collection_uuid));
|
joinable!(users_collections -> collections (collection_uuid));
|
||||||
joinable!(users_collections -> users (user_uuid));
|
joinable!(users_collections -> users (user_uuid));
|
||||||
|
@ -193,6 +218,7 @@ allow_tables_to_appear_in_same_query!(
|
||||||
invitations,
|
invitations,
|
||||||
org_policies,
|
org_policies,
|
||||||
organizations,
|
organizations,
|
||||||
|
sends,
|
||||||
twofactor,
|
twofactor,
|
||||||
users,
|
users,
|
||||||
users_collections,
|
users_collections,
|
||||||
|
|
|
@ -102,6 +102,29 @@ table! {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
table! {
|
||||||
|
sends (uuid) {
|
||||||
|
uuid -> Text,
|
||||||
|
user_uuid -> Nullable<Text>,
|
||||||
|
organization_uuid -> Nullable<Text>,
|
||||||
|
name -> Text,
|
||||||
|
notes -> Nullable<Text>,
|
||||||
|
atype -> Integer,
|
||||||
|
data -> Text,
|
||||||
|
key -> Text,
|
||||||
|
password_hash -> Nullable<Binary>,
|
||||||
|
password_salt -> Nullable<Binary>,
|
||||||
|
password_iter -> Nullable<Integer>,
|
||||||
|
max_access_count -> Nullable<Integer>,
|
||||||
|
access_count -> Integer,
|
||||||
|
creation_date -> Timestamp,
|
||||||
|
revision_date -> Timestamp,
|
||||||
|
expiration_date -> Nullable<Timestamp>,
|
||||||
|
deletion_date -> Timestamp,
|
||||||
|
disabled -> Bool,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
table! {
|
table! {
|
||||||
twofactor (uuid) {
|
twofactor (uuid) {
|
||||||
uuid -> Text,
|
uuid -> Text,
|
||||||
|
@ -176,6 +199,8 @@ joinable!(folders -> users (user_uuid));
|
||||||
joinable!(folders_ciphers -> ciphers (cipher_uuid));
|
joinable!(folders_ciphers -> ciphers (cipher_uuid));
|
||||||
joinable!(folders_ciphers -> folders (folder_uuid));
|
joinable!(folders_ciphers -> folders (folder_uuid));
|
||||||
joinable!(org_policies -> organizations (org_uuid));
|
joinable!(org_policies -> organizations (org_uuid));
|
||||||
|
joinable!(sends -> organizations (organization_uuid));
|
||||||
|
joinable!(sends -> users (user_uuid));
|
||||||
joinable!(twofactor -> users (user_uuid));
|
joinable!(twofactor -> users (user_uuid));
|
||||||
joinable!(users_collections -> collections (collection_uuid));
|
joinable!(users_collections -> collections (collection_uuid));
|
||||||
joinable!(users_collections -> users (user_uuid));
|
joinable!(users_collections -> users (user_uuid));
|
||||||
|
@ -193,6 +218,7 @@ allow_tables_to_appear_in_same_query!(
|
||||||
invitations,
|
invitations,
|
||||||
org_policies,
|
org_policies,
|
||||||
organizations,
|
organizations,
|
||||||
|
sends,
|
||||||
twofactor,
|
twofactor,
|
||||||
users,
|
users,
|
||||||
users_collections,
|
users_collections,
|
||||||
|
|
12
src/error.rs
12
src/error.rs
|
@ -220,6 +220,18 @@ macro_rules! err {
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! err_code {
|
||||||
|
($msg:expr, $err_code: literal) => {{
|
||||||
|
error!("{}", $msg);
|
||||||
|
return Err(crate::error::Error::new($msg, $msg).with_code($err_code));
|
||||||
|
}};
|
||||||
|
($usr_msg:expr, $log_value:expr, $err_code: literal) => {{
|
||||||
|
error!("{}. {}", $usr_msg, $log_value);
|
||||||
|
return Err(crate::error::Error::new($usr_msg, $log_value).with_code($err_code));
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! err_discard {
|
macro_rules! err_discard {
|
||||||
($msg:expr, $data:expr) => {{
|
($msg:expr, $data:expr) => {{
|
||||||
|
|
Loading…
Reference in a new issue