From 5751463942cc91f1f1ffaf6e2ac633d7a0085f25 Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Tue, 13 Apr 2021 19:14:07 +0100 Subject: Add history sync, resolves #13 (#31) * Add encryption * Add login and register command * Add count endpoint * Write initial sync push * Add single sync command Confirmed working for one client only * Automatically sync on a configurable frequency * Add key command, key arg to login * Only load session if it exists * Use sync and history timestamps for download * Bind other key code Seems like some systems have this code for up arrow? I'm not sure why, and it's not an easy one to google. * Simplify upload * Try and fix download sync loop * Change sync order to avoid uploading what we just downloaded * Multiline import fix * Fix time parsing * Fix importing history with no time * Add hostname to sync * Use hostname to filter sync * Fixes * Add binding * Stuff from yesterday * Set cursor modes * Make clippy happy * Bump version --- Cargo.lock | 958 +++++++++++++++++++-- Cargo.toml | 14 +- config.toml | 65 +- migrations/2021-03-20-151809_create_history/up.sql | 6 +- migrations/2021-03-20-171007_create_users/up.sql | 5 + src/api.rs | 36 + src/command/history.rs | 30 +- src/command/login.rs | 48 ++ src/command/mod.rs | 34 +- src/command/register.rs | 54 ++ src/command/search.rs | 3 +- src/command/server.rs | 4 +- src/command/sync.rs | 15 + src/local/api_client.rs | 94 ++ src/local/database.rs | 55 +- src/local/encryption.rs | 108 +++ src/local/history.rs | 11 +- src/local/import.rs | 118 ++- src/local/mod.rs | 3 + src/local/sync.rs | 135 +++ src/main.rs | 19 +- src/remote/auth.rs | 92 +- src/remote/database.rs | 2 +- src/remote/models.rs | 16 +- src/remote/server.rs | 26 +- src/remote/views.rs | 144 +++- src/schema.rs | 4 +- src/settings.rs | 131 ++- src/shell/atuin.zsh | 26 +- src/utils.rs | 24 + 30 files changed, 2018 insertions(+), 262 deletions(-) create mode 100644 src/api.rs create mode 100644 src/command/login.rs create mode 100644 src/command/register.rs create mode 100644 src/command/sync.rs create mode 100644 src/local/api_client.rs create mode 100644 src/local/encryption.rs create mode 100644 src/local/sync.rs create mode 100644 src/utils.rs diff --git a/Cargo.lock b/Cargo.lock index b4c2857a..e83d5303 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -106,8 +106,9 @@ dependencies = [ [[package]] name = "atuin" -version = "0.4.0" +version = "0.5.0" dependencies = [ + "base64 0.13.0", "chrono", "chrono-english", "cli-table", @@ -118,15 +119,21 @@ dependencies = [ "dotenv", "eyre", "fern", - "hostname", + "fork", "indicatif", "itertools", "log 0.4.14", + "parse_duration", + "rand 0.8.3", + "reqwest", + "rmp-serde", "rocket", "rocket_contrib", "rusqlite", + "rust-crypto", "serde 1.0.125", "serde_derive", + "serde_json", "shellexpand", "sodiumoxide", "structopt", @@ -134,6 +141,7 @@ dependencies = [ "tui", "unicode-width", "uuid", + "whoami", ] [[package]] @@ -223,6 +231,12 @@ dependencies = [ "serde 1.0.125", ] +[[package]] +name = "bumpalo" +version = "3.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63396b8a4b9de3f4fdfb320ab6080762242f66a8ef174c49d8e19b674db4cdbe" + [[package]] name = "byte-tools" version = "0.3.1" @@ -235,6 +249,12 @@ version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae44d1a3d5a19df61dd0c8beb138458ac2a53a7ac09eba97d55592540004306b" +[[package]] +name = "bytes" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b700ce4376041dcd0a327fd0097c41095743c4c8af8887265942faf1100bd040" + [[package]] name = "cassowary" version = "0.3.0" @@ -268,6 +288,7 @@ dependencies = [ "libc", "num-integer", "num-traits 0.2.14", + "serde 1.0.125", "time", "winapi 0.3.9", ] @@ -316,9 +337,9 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "efe942512e068e15991cbcef4e8182884555febbb21b5b4faf5dd5561850141a" dependencies = [ - "proc-macro2 1.0.24", + "proc-macro2 1.0.26", "quote 1.0.9", - "syn 1.0.60", + "syn 1.0.69", ] [[package]] @@ -369,11 +390,27 @@ dependencies = [ "hkdf", "hmac", "percent-encoding 2.1.0", - "rand", + "rand 0.7.3", "sha2", "time", ] +[[package]] +name = "core-foundation" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a89e2ae426ea83155dccf10c0fa6b1463ef6d5fcb44cee0b224a408fa640a62" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea221b5284a47e40033bf9b66f35f984ec0ea2931eb03505246cd27a963f981b" + [[package]] name = "crossbeam-utils" version = "0.8.1" @@ -469,9 +506,9 @@ version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45f5098f628d02a7a0f68ddba586fb61e80edec3bdc1be3b921f4ceec60858d3" dependencies = [ - "proc-macro2 1.0.24", + "proc-macro2 1.0.26", "quote 1.0.9", - "syn 1.0.60", + "syn 1.0.69", ] [[package]] @@ -552,6 +589,15 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" +[[package]] +name = "encoding_rs" +version = "0.8.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80df024fbc5ac80f87dfef0d9f5209a252f2a497f7f42944cff24d8253cac065" +dependencies = [ + "cfg-if 1.0.0", +] + [[package]] name = "eyre" version = "0.6.5" @@ -601,6 +647,46 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "fork" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4c5b9b0bce249a456f83ac4404e8baad0d2ba81cf651949719a4f74eb7323bb" +dependencies = [ + "libc", +] + +[[package]] +name = "form_urlencoded" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +dependencies = [ + "matches", + "percent-encoding 2.1.0", +] + [[package]] name = "fsevent" version = "0.4.0" @@ -620,6 +706,12 @@ dependencies = [ "libc", ] +[[package]] +name = "fuchsia-cprng" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" + [[package]] name = "fuchsia-zircon" version = "0.3.3" @@ -636,6 +728,60 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" +[[package]] +name = "futures-channel" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c2dd2df839b57db9ab69c2c9d8f3e8c81984781937fe2807dc6dcf3b2ad2939" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15496a72fabf0e62bdc3df11a59a3787429221dd0710ba8ef163d6f7a9112c94" + +[[package]] +name = "futures-io" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71c2c65c57704c32f5241c1223167c2c3294fd34ac020c807ddbe6db287ba59" + +[[package]] +name = "futures-sink" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85754d98985841b7d4f5e8e6fbfa4a4ac847916893ec511a2917ccd8525b8bb3" + +[[package]] +name = "futures-task" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa189ef211c15ee602667a6fcfe1c1fd9e07d42250d2156382820fba33c9df80" + +[[package]] +name = "futures-util" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1812c7ab8aedf8d6f2701a43e1243acdbcc2b36ab26e2ad421eb99ac963d96d1" +dependencies = [ + "futures-core", + "futures-io", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "gcc" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" + [[package]] name = "generic-array" version = "0.12.3" @@ -682,6 +828,25 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" +[[package]] +name = "h2" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc018e188373e2777d0ef2467ebff62a08e66c3f5857b23c8fbec3018210dc00" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "hashbrown" version = "0.9.1" @@ -739,14 +904,25 @@ dependencies = [ ] [[package]] -name = "hostname" -version = "0.3.1" +name = "http" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" +checksum = "527e8c9ac747e28542699a951517aa9a6945af506cd1f2e1b53a576c17b6cc11" dependencies = [ - "libc", - "match_cfg", - "winapi 0.3.9", + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfb77c123b4e2f72a2069aeae0b4b4949cc7e966df277813fc16347e7549737" +dependencies = [ + "bytes", + "http", + "pin-project-lite", ] [[package]] @@ -755,6 +931,12 @@ version = "1.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "615caabe2c3160b313d52ccc905335f4ed5f10881dd63dc5699d47e90be85691" +[[package]] +name = "httpdate" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "494b4d60369511e7dea41cf646832512a94e542f68bb9c49e54518e0f468eb47" + [[package]] name = "hyper" version = "0.10.16" @@ -765,13 +947,50 @@ dependencies = [ "httparse", "language-tags", "log 0.3.9", - "mime", + "mime 0.2.6", "num_cpus", "time", "traitobject", "typeable", "unicase", - "url", + "url 1.7.2", +] + +[[package]] +name = "hyper" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bf09f61b52cfcf4c00de50df88ae423d6c02354e385a86341133b5338630ad1" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper 0.14.5", + "native-tls", + "tokio", + "tokio-native-tls", ] [[package]] @@ -785,6 +1004,17 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "idna" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89829a5d69c23d348314a7ac337fe39173b61149a9864deabd260983aed48c21" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "indenter" version = "0.3.2" @@ -851,6 +1081,12 @@ dependencies = [ "libc", ] +[[package]] +name = "ipnet" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47be2f14c678be2fdcab04ab1171db51b2762ce6f0a8ee87c8dd4a04ed216135" + [[package]] name = "itertools" version = "0.10.0" @@ -866,6 +1102,15 @@ version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" +[[package]] +name = "js-sys" +version = "0.3.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d99f9e3e84b8f67f846ef5b4cbbc3b1c29f6c759fcbce6f01aa0e73d932a24c" +dependencies = [ + "wasm-bindgen", +] + [[package]] name = "kernel32-sys" version = "0.2.2" @@ -968,12 +1213,6 @@ dependencies = [ "cfg-if 1.0.0", ] -[[package]] -name = "match_cfg" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" - [[package]] name = "matches" version = "0.1.8" @@ -1002,9 +1241,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9753f12909fd8d923f75ae5c3258cae1ed3c8ec052e1b38c93c21a6d157f789c" dependencies = [ "migrations_internals", - "proc-macro2 1.0.24", + "proc-macro2 1.0.26", "quote 1.0.9", - "syn 1.0.60", + "syn 1.0.69", ] [[package]] @@ -1016,6 +1255,12 @@ dependencies = [ "log 0.3.9", ] +[[package]] +name = "mime" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" + [[package]] name = "mio" version = "0.6.23" @@ -1029,12 +1274,25 @@ dependencies = [ "kernel32-sys", "libc", "log 0.4.14", - "miow", + "miow 0.2.2", "net2", "slab", "winapi 0.2.8", ] +[[package]] +name = "mio" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf80d3e903b34e0bd7282b218398aec54e082c840d9baf8339e0080a0c542956" +dependencies = [ + "libc", + "log 0.4.14", + "miow 0.3.7", + "ntapi", + "winapi 0.3.9", +] + [[package]] name = "mio-extras" version = "2.0.6" @@ -1043,7 +1301,7 @@ checksum = "52403fe290012ce777c4626790c8951324a2b9e3316b3143779c72b029742f19" dependencies = [ "lazycell", "log 0.4.14", - "mio", + "mio 0.6.23", "slab", ] @@ -1059,6 +1317,33 @@ dependencies = [ "ws2_32-sys", ] +[[package]] +name = "miow" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" +dependencies = [ + "winapi 0.3.9", +] + +[[package]] +name = "native-tls" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8d96b2e1c8da3957d58100b09f102c6d9cfdfced01b7ec5a8974044bb09dbd4" +dependencies = [ + "lazy_static", + "libc", + "log 0.4.14", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "net2" version = "0.2.37" @@ -1093,12 +1378,56 @@ dependencies = [ "fsevent-sys", "inotify", "libc", - "mio", + "mio 0.6.23", "mio-extras", "walkdir", "winapi 0.3.9", ] +[[package]] +name = "ntapi" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" +dependencies = [ + "winapi 0.3.9", +] + +[[package]] +name = "num" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8536030f9fea7127f841b45bb6243b27255787fb4eb83958aa1ef9d2fdc0c36" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits 0.2.14", +] + +[[package]] +name = "num-bigint" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304" +dependencies = [ + "autocfg", + "num-integer", + "num-traits 0.2.14", +] + +[[package]] +name = "num-complex" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6b19411a9719e753aff12e5187b74d60d3dc449ec3f4dc21e3989c3f554bc95" +dependencies = [ + "autocfg", + "num-traits 0.2.14", +] + [[package]] name = "num-integer" version = "0.1.44" @@ -1109,6 +1438,29 @@ dependencies = [ "num-traits 0.2.14", ] +[[package]] +name = "num-iter" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59" +dependencies = [ + "autocfg", + "num-integer", + "num-traits 0.2.14", +] + +[[package]] +name = "num-rational" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef" +dependencies = [ + "autocfg", + "num-bigint", + "num-integer", + "num-traits 0.2.14", +] + [[package]] name = "num-traits" version = "0.1.43" @@ -1161,6 +1513,39 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" +[[package]] +name = "openssl" +version = "0.10.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a61075b62a23fef5a29815de7536d940aa35ce96d18ce0cc5076272db678a577" +dependencies = [ + "bitflags", + "cfg-if 1.0.0", + "foreign-types", + "libc", + "once_cell", + "openssl-sys", +] + +[[package]] +name = "openssl-probe" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de" + +[[package]] +name = "openssl-sys" +version = "0.9.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "313752393519e876837e09e1fa183ddef0be7735868dced3196f4472d536277f" +dependencies = [ + "autocfg", + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "parking_lot" version = "0.11.1" @@ -1186,6 +1571,17 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "parse_duration" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7037e5e93e0172a5a96874380bf73bc6ecef022e26fa25f2be26864d6b3ba95d" +dependencies = [ + "lazy_static", + "num", + "regex", +] + [[package]] name = "pear" version = "0.1.4" @@ -1221,8 +1617,40 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" [[package]] -name = "pkg-config" -version = "0.3.19" +name = "pin-project" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc174859768806e91ae575187ada95c91a29e96a98dc5d2cd9a1fed039501ba6" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a490329918e856ed1b083f244e3bfe2d8c4f336407e4ea9e1a9f479ff09049e5" +dependencies = [ + "proc-macro2 1.0.26", + "quote 1.0.9", + "syn 1.0.69", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc0e1f259c92177c30a4c9d177246edd0a3568b25756a977d0632cf8fa37e905" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c" @@ -1258,9 +1686,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" dependencies = [ "proc-macro-error-attr", - "proc-macro2 1.0.24", + "proc-macro2 1.0.26", "quote 1.0.9", - "syn 1.0.60", + "syn 1.0.69", "version_check 0.9.2", ] @@ -1270,7 +1698,7 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" dependencies = [ - "proc-macro2 1.0.24", + "proc-macro2 1.0.26", "quote 1.0.9", "version_check 0.9.2", ] @@ -1286,9 +1714,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.24" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" +checksum = "a152013215dca273577e18d2bf00fa862b89b24169fb78c4c95aeb07992c9cec" dependencies = [ "unicode-xid 0.2.1", ] @@ -1308,7 +1736,7 @@ version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" dependencies = [ - "proc-macro2 1.0.24", + "proc-macro2 1.0.26", ] [[package]] @@ -1322,6 +1750,29 @@ dependencies = [ "scheduled-thread-pool", ] +[[package]] +name = "rand" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ac302d8f83c0c1974bf758f6b041c6c8ada916fbb44a609158ca8b064cc76c" +dependencies = [ + "libc", + "rand 0.4.6", +] + +[[package]] +name = "rand" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" +dependencies = [ + "fuchsia-cprng", + "libc", + "rand_core 0.3.1", + "rdrand", + "winapi 0.3.9", +] + [[package]] name = "rand" version = "0.7.3" @@ -1330,9 +1781,21 @@ checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" dependencies = [ "getrandom 0.1.16", "libc", - "rand_chacha", - "rand_core", - "rand_hc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc 0.2.0", +] + +[[package]] +name = "rand" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e" +dependencies = [ + "libc", + "rand_chacha 0.3.0", + "rand_core 0.6.2", + "rand_hc 0.3.0", ] [[package]] @@ -1342,9 +1805,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.5.1", +] + +[[package]] +name = "rand_chacha" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.2", ] +[[package]] +name = "rand_core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" +dependencies = [ + "rand_core 0.4.2", +] + +[[package]] +name = "rand_core" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" + [[package]] name = "rand_core" version = "0.5.1" @@ -1354,13 +1842,40 @@ dependencies = [ "getrandom 0.1.16", ] +[[package]] +name = "rand_core" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7" +dependencies = [ + "getrandom 0.2.2", +] + [[package]] name = "rand_hc" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" dependencies = [ - "rand_core", + "rand_core 0.5.1", +] + +[[package]] +name = "rand_hc" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73" +dependencies = [ + "rand_core 0.6.2", +] + +[[package]] +name = "rdrand" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" +dependencies = [ + "rand_core 0.3.1", ] [[package]] @@ -1435,6 +1950,71 @@ version = "0.6.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5eb417147ba9860a96cfe72a0b93bf88fee1744b5636ec99ab20c1aa9376581" +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi 0.3.9", +] + +[[package]] +name = "reqwest" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf12057f289428dbf5c591c74bf10392e4a8003f993405a902f20117019022d4" +dependencies = [ + "base64 0.13.0", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "http", + "http-body", + "hyper 0.14.5", + "hyper-tls", + "ipnet", + "js-sys", + "lazy_static", + "log 0.4.14", + "mime 0.3.16", + "native-tls", + "percent-encoding 2.1.0", + "pin-project-lite", + "serde 1.0.125", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-native-tls", + "url 2.2.1", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", +] + +[[package]] +name = "rmp" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f55e5fa1446c4d5dd1f5daeed2a4fe193071771a2636274d0d7a3b082aa7ad6" +dependencies = [ + "byteorder", + "num-traits 0.2.14", +] + +[[package]] +name = "rmp-serde" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "839395ef53057db96b84c9238ab29e1a13f2e5c8ec9f66bef853ab4197303924" +dependencies = [ + "byteorder", + "rmp", + "serde 1.0.125", +] + [[package]] name = "rocket" version = "0.4.7" @@ -1506,7 +2086,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce364100ed7a1bf39257b69ebd014c1d5b4979b0d365d8c9ab0aa9c79645493d" dependencies = [ "cookie", - "hyper", + "hyper 0.10.16", "indexmap", "pear", "percent-encoding 1.0.1", @@ -1543,12 +2123,31 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "rust-crypto" +version = "0.2.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f76d05d3993fd5f4af9434e8e436db163a12a9d40e1a58a726f27a01dfd12a2a" +dependencies = [ + "gcc", + "libc", + "rand 0.3.23", + "rustc-serialize", + "time", +] + [[package]] name = "rust-ini" version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e52c148ef37f8c375d49d5a73aa70713125b7f19095948a923f80afdeb22ec2" +[[package]] +name = "rustc-serialize" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda" + [[package]] name = "ryu" version = "1.0.5" @@ -1576,6 +2175,16 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "088c5d71572124929ea7549a8ce98e1a6fd33d0a38367b09027b382e67c033db" +[[package]] +name = "schannel" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75" +dependencies = [ + "lazy_static", + "winapi 0.3.9", +] + [[package]] name = "scheduled-thread-pool" version = "0.2.5" @@ -1591,6 +2200,29 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "security-framework" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3670b1d2fdf6084d192bc71ead7aabe6c06aa2ea3fbd9cc3ac111fa5c2b1bd84" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3676258fd3cfe2c9a0ec99ce3038798d847ce3e4bb17746373eb9f0f1ac16339" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "serde" version = "0.8.23" @@ -1621,17 +2253,29 @@ version = "1.0.125" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b093b7a2bb58203b5da3056c05b4ec1fed827dcfdb37347a8841695263b3d06d" dependencies = [ - "proc-macro2 1.0.24", + "proc-macro2 1.0.26", "quote 1.0.9", - "syn 1.0.60", + "syn 1.0.69", ] [[package]] name = "serde_json" -version = "1.0.62" +version = "1.0.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79" +dependencies = [ + "itoa", + "ryu", + "serde 1.0.125", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea1c6153794552ea7cf7cf63b1231a25de00ec90db326ba6264440fa08e31486" +checksum = "edfa57a7f8d9c1d260a549e7224100f6c43d43f9103e06dd8b4095a9b2b43ce9" dependencies = [ + "form_urlencoded", "itoa", "ryu", "serde 1.0.125", @@ -1670,6 +2314,16 @@ version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" +[[package]] +name = "socket2" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e3dfc207c526015c632472a77be09cf1b6e46866581aecae5cc38fb4235dea2" +dependencies = [ + "libc", + "winapi 0.3.9", +] + [[package]] name = "sodiumoxide" version = "0.2.6" @@ -1718,9 +2372,9 @@ checksum = "5ba9cdfda491b814720b6b06e0cac513d922fc407582032e8706e9f137976f90" dependencies = [ "heck", "proc-macro-error", - "proc-macro2 1.0.24", + "proc-macro2 1.0.26", "quote 1.0.9", - "syn 1.0.60", + "syn 1.0.69", ] [[package]] @@ -1748,15 +2402,29 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.60" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c700597eca8a5a762beb35753ef6b94df201c81cca676604f547495a0d7f0081" +checksum = "48fe99c6bd8b1cc636890bcc071842de909d902c81ac7dab53ba33c421ab8ffb" dependencies = [ - "proc-macro2 1.0.24", + "proc-macro2 1.0.26", "quote 1.0.9", "unicode-xid 0.2.1", ] +[[package]] +name = "tempfile" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "rand 0.8.3", + "redox_syscall 0.2.4", + "remove_dir_all", + "winapi 0.3.9", +] + [[package]] name = "termcolor" version = "1.1.2" @@ -1831,6 +2499,45 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" +[[package]] +name = "tokio" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "134af885d758d645f0f0505c9a8b3f9bf8a348fd822e112ab5248138348f1722" +dependencies = [ + "autocfg", + "bytes", + "libc", + "memchr", + "mio 0.7.11", + "num_cpus", + "pin-project-lite", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5143d049e85af7fbc36f5454d990e62c2df705b3589f123b71f441b6b59f443f" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "log 0.4.14", + "pin-project-lite", + "tokio", +] + [[package]] name = "toml" version = "0.4.10" @@ -1849,12 +2556,44 @@ dependencies = [ "serde 1.0.125", ] +[[package]] +name = "tower-service" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" + +[[package]] +name = "tracing" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01ebdc2bb4498ab1ab5f5b73c5803825e60199229ccba0698170e3be0e7f959f" +dependencies = [ + "cfg-if 1.0.0", + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f50de3927f93d202783f4513cda820ab47ef17f624b03c096e86ef00c67e6b5f" +dependencies = [ + "lazy_static", +] + [[package]] name = "traitobject" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "efd1f82c56340fdf16f2a953d7bda4f8fdffba13d93b00844c25572110b26079" +[[package]] +name = "try-lock" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" + [[package]] name = "tui" version = "0.14.0" @@ -1947,11 +2686,23 @@ version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a" dependencies = [ - "idna", + "idna 0.1.5", "matches", "percent-encoding 1.0.1", ] +[[package]] +name = "url" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ccd964113622c8e9322cfac19eb1004a07e636c545f325da085d5cdde6f1f8b" +dependencies = [ + "form_urlencoded", + "idna 0.2.2", + "matches", + "percent-encoding 2.1.0", +] + [[package]] name = "uuid" version = "0.8.2" @@ -1996,6 +2747,16 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log 0.4.14", + "try-lock", +] + [[package]] name = "wasi" version = "0.9.0+wasi-snapshot-preview1" @@ -2008,6 +2769,94 @@ version = "0.10.2+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" +[[package]] +name = "wasm-bindgen" +version = "0.2.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83240549659d187488f91f33c0f8547cbfef0b2088bc470c116d1d260ef623d9" +dependencies = [ + "cfg-if 1.0.0", + "serde 1.0.125", + "serde_json", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae70622411ca953215ca6d06d3ebeb1e915f0f6613e3b495122878d7ebec7dae" +dependencies = [ + "bumpalo", + "lazy_static", + "log 0.4.14", + "proc-macro2 1.0.26", + "quote 1.0.9", + "syn 1.0.69", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81b8b767af23de6ac18bf2168b690bed2902743ddf0fb39252e36f9e2bfc63ea" +dependencies = [ + "cfg-if 1.0.0", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e734d91443f177bfdb41969de821e15c516931c3c3db3d318fa1b68975d0f6f" +dependencies = [ + "quote 1.0.9", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d53739ff08c8a68b0fdbcd54c372b8ab800b1449ab3c9d706503bc7dd1621b2c" +dependencies = [ + "proc-macro2 1.0.26", + "quote 1.0.9", + "syn 1.0.69", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9a543ae66aa233d14bb765ed9af4a33e81b8b58d1584cf1b47ff8cd0b9e4489" + +[[package]] +name = "web-sys" +version = "0.3.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a905d57e488fec8861446d3393670fb50d27a262344013181c2cdf9fff5481be" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "whoami" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4abacf325c958dfeaf1046931d37f2a901b6dfe0968ee965a29e94c6766b2af6" +dependencies = [ + "wasm-bindgen", + "web-sys", +] + [[package]] name = "winapi" version = "0.2.8" @@ -2051,6 +2900,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "winreg" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69" +dependencies = [ + "winapi 0.3.9", +] + [[package]] name = "ws2_32-sys" version = "0.2.1" diff --git a/Cargo.toml b/Cargo.toml index a016024a..9d337878 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "atuin" -version = "0.4.0" +version = "0.5.0" authors = ["Ellie Huxtable "] edition = "2018" license = "MIT" @@ -9,20 +9,22 @@ description = "atuin - magical shell history" [dependencies] log = "0.4" fern = "0.6.0" -chrono = "0.4" +chrono = { version = "0.4", features = ["serde"] } eyre = "0.6" shellexpand = "2" structopt = "0.3" directories = "3" uuid = { version = "0.8", features = ["v4"] } indicatif = "0.15.0" -hostname = "0.3.1" +whoami = "1.1.2" rocket = "0.4.7" chrono-english = "0.1.4" cli-table = "0.4" config = "0.11" serde_derive = "1.0.125" serde = "1.0.125" +serde_json = "1.0.64" +rmp-serde = "0.15.4" tui = "0.14" termion = "1.5" unicode-width = "0.1" @@ -31,6 +33,12 @@ diesel = { version = "1.4.4", features = ["postgres", "chrono"] } diesel_migrations = "1.4.0" dotenv = "0.15.0" sodiumoxide = "0.2.6" +reqwest = { version = "0.11", features = ["blocking", "json"] } +base64 = "0.13.0" +fork = "0.1.18" +parse_duration = "2.1.1" +rand = "0.8.3" +rust-crypto = "^0.2" [dependencies.rusqlite] version = "0.25" diff --git a/config.toml b/config.toml index 19a454e2..9d5452cb 100644 --- a/config.toml +++ b/config.toml @@ -3,36 +3,41 @@ # This section specifies the config for a local client, # ie where your shell history is on your local machine [local] -# (optional) -# where to store your database, default is your system data directory -# mac: ~/Library/Application Support/com.elliehuxtable.atuin/history.db -# linux: ~/.local/share/atuin/history.db -db_path = "~/.history.db" -# (optional, default us) -# date format used, either "us" or "uk" -dialect = "uk" -# (optional, default false) -# whether to enable sync of history. requires authentication -sync = false -# (optional, default 5m) -# how often to sync history. note that this is only triggered when a command is ran, and the last sync was >= this value ago -# set it to 0 to sync after every command -sync_frequency = "5m" -# (optional, default https://atuin.elliehuxtable.com) -# address of the sync server -sync_address = "https://atuin.elliehuxtable.com" +## where to store your database, default is your system data directory +## mac: ~/Library/Application Support/com.elliehuxtable.atuin/history.db +## linux: ~/.local/share/atuin/history.db +# db_path = "~/.history.db" + +## where to store your encryption key, default is your system data directory +# key_path = "~/.key" + +## where to store your auth session token, default is your system data directory +# session_path = "~/.key" + +## date format used, either "us" or "uk" +# dialect = "uk" + +## enable or disable automatic sync +# auto_sync = true + +## how often to sync history. note that this is only triggered when a command +## is ran, so sync intervals may well be longer +## set it to 0 to sync after every command +# sync_frequency = "5m" + +## address of the sync server +# sync_address = "https://api.atuin.sh" # This section configures the sync server, if you decide to host your own [remote] -# (optional, default 127.0.0.1) -# host to bind, can also be passed via CLI args -host = "127.0.0.1" -# (optional, default 8888) -# port to bind, can also be passed via CLI args -port = 8888 -# (optional, default false) -# whether to allow anyone to register an account -open_registration = false -# (required) -# URI for postgres (using development creds here) -db_uri="postgres://username:password@localhost/atuin" +## host to bind, can also be passed via CLI args +# host = "127.0.0.1" + +## port to bind, can also be passed via CLI args +# port = 8888 + +## whether to allow anyone to register an account +# open_registration = false + +## URI for postgres (using development creds here) +# db_uri="postgres://username:password@localhost/atuin" diff --git a/migrations/2021-03-20-151809_create_history/up.sql b/migrations/2021-03-20-151809_create_history/up.sql index 7cb19fc7..4192b04d 100644 --- a/migrations/2021-03-20-151809_create_history/up.sql +++ b/migrations/2021-03-20-151809_create_history/up.sql @@ -4,8 +4,10 @@ create table history ( id bigserial primary key, client_id text not null unique, -- the client-generated ID user_id bigserial not null, -- allow multiple users - mac varchar(128) not null, -- store a hashed mac address, to identify machines - more likely to be unique than hostname + hostname text not null, -- a unique identifier from the client (can be hashed, random, whatever) timestamp timestamp not null, -- one of the few non-encrypted metadatas - data varchar(8192) not null -- store the actual history data, encrypted. I don't wanna know! + data varchar(8192) not null, -- store the actual history data, encrypted. I don't wanna know! + + created_at timestamp not null default current_timestamp ); diff --git a/migrations/2021-03-20-171007_create_users/up.sql b/migrations/2021-03-20-171007_create_users/up.sql index 0eecea7c..46c6a372 100644 --- a/migrations/2021-03-20-171007_create_users/up.sql +++ b/migrations/2021-03-20-171007_create_users/up.sql @@ -1,6 +1,11 @@ -- Your SQL goes here create table users ( id bigserial primary key, -- also store our own ID + username varchar(32) not null unique, -- being able to contact users is useful email varchar(128) not null unique, -- being able to contact users is useful password varchar(128) not null unique ); + +-- the prior index is case sensitive :( +CREATE UNIQUE INDEX email_unique_idx on users (LOWER(email)); +CREATE UNIQUE INDEX username_unique_idx on users (LOWER(username)); diff --git a/src/api.rs b/src/api.rs new file mode 100644 index 00000000..90977404 --- /dev/null +++ b/src/api.rs @@ -0,0 +1,36 @@ +use chrono::Utc; + +// This is shared between the client and the server, and has the data structures +// representing the requests/responses for each method. +// TODO: Properly define responses rather than using json! + +#[derive(Debug, Serialize, Deserialize)] +pub struct RegisterRequest { + pub email: String, + pub username: String, + pub password: String, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct LoginRequest { + pub username: String, + pub password: String, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct AddHistoryRequest { + pub id: String, + pub timestamp: chrono::DateTime, + pub data: String, + pub hostname: String, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct CountResponse { + pub count: i64, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct ListHistoryResponse { + pub history: Vec, +} diff --git a/src/command/history.rs b/src/command/history.rs index 05aed4b9..3b4a717c 100644 --- a/src/command/history.rs +++ b/src/command/history.rs @@ -1,10 +1,13 @@ use std::env; use eyre::Result; +use fork::{fork, Fork}; use structopt::StructOpt; use crate::local::database::Database; use crate::local::history::History; +use crate::local::sync; +use crate::settings::Settings; #[derive(StructOpt)] pub enum Cmd { @@ -50,21 +53,13 @@ fn print_list(h: &[History]) { } impl Cmd { - pub fn run(&self, db: &mut impl Database) -> Result<()> { + pub fn run(&self, settings: &Settings, db: &mut impl Database) -> Result<()> { match self { Self::Start { command: words } => { let command = words.join(" "); let cwd = env::current_dir()?.display().to_string(); - let h = History::new( - chrono::Utc::now().timestamp_nanos(), - command, - cwd, - -1, - -1, - None, - None, - ); + let h = History::new(chrono::Utc::now(), command, cwd, -1, -1, None, None); // print the ID // we use this as the key for calling end @@ -76,10 +71,23 @@ impl Cmd { Self::End { id, exit } => { let mut h = db.load(id)?; h.exit = *exit; - h.duration = chrono::Utc::now().timestamp_nanos() - h.timestamp; + h.duration = chrono::Utc::now().timestamp_nanos() - h.timestamp.timestamp_nanos(); db.update(&h)?; + if settings.local.should_sync()? { + match fork() { + Ok(Fork::Parent(child)) => { + debug!("launched sync background process with PID {}", child); + } + Ok(Fork::Child) => { + debug!("running periodic background sync"); + sync::sync(settings, false, db)?; + } + Err(_) => println!("Fork failed"), + } + } + Ok(()) } diff --git a/src/command/login.rs b/src/command/login.rs new file mode 100644 index 00000000..4f58b77f --- /dev/null +++ b/src/command/login.rs @@ -0,0 +1,48 @@ +use std::collections::HashMap; +use std::fs::File; +use std::io::prelude::*; + +use eyre::Result; +use structopt::StructOpt; + +use crate::settings::Settings; + +#[derive(StructOpt)] +#[structopt(setting(structopt::clap::AppSettings::DeriveDisplayOrder))] +pub struct Cmd { + #[structopt(long, short)] + pub username: String, + + #[structopt(long, short)] + pub password: String, + + #[structopt(long, short, about = "the encryption key for your account")] + pub key: String, +} + +impl Cmd { + pub fn run(&self, settings: &Settings) -> Result<()> { + let mut map = HashMap::new(); + map.insert("username", self.username.clone()); + map.insert("password", self.password.clone()); + + let url = format!("{}/login", settings.local.sync_address); + let client = reqwest::blocking::Client::new(); + let resp = client.post(url).json(&map).send()?; + + let session = resp.json::>()?; + let session = session["session"].clone(); + + let session_path = settings.local.session_path.as_str(); + let mut file = File::create(session_path)?; + file.write_all(session.as_bytes())?; + + let key_path = settings.local.key_path.as_str(); + let mut file = File::create(key_path)?; + file.write_all(&base64::decode(self.key.clone())?)?; + + println!("Logged in!"); + + Ok(()) + } +} diff --git a/src/command/mod.rs b/src/command/mod.rs index a5ea0228..eeb11a87 100644 --- a/src/command/mod.rs +++ b/src/command/mod.rs @@ -9,9 +9,12 @@ mod event; mod history; mod import; mod init; +mod login; +mod register; mod search; mod server; mod stats; +mod sync; #[derive(StructOpt)] pub enum AtuinCmd { @@ -38,6 +41,21 @@ pub enum AtuinCmd { #[structopt(about = "interactive history search")] Search { query: Vec }, + + #[structopt(about = "sync with the configured server")] + Sync { + #[structopt(long, short, about = "force re-download everything")] + force: bool, + }, + + #[structopt(about = "login to the configured server")] + Login(login::Cmd), + + #[structopt(about = "register with the configured server")] + Register(register::Cmd), + + #[structopt(about = "print the encryption key for transfer to another machine")] + Key, } pub fn uuid_v4() -> String { @@ -47,13 +65,27 @@ pub fn uuid_v4() -> String { impl AtuinCmd { pub fn run(self, db: &mut impl Database, settings: &Settings) -> Result<()> { match self { - Self::History(history) => history.run(db), + Self::History(history) => history.run(settings, db), Self::Import(import) => import.run(db), Self::Server(server) => server.run(settings), Self::Stats(stats) => stats.run(db, settings), Self::Init => init::init(), Self::Search { query } => search::run(&query, db), + Self::Sync { force } => sync::run(settings, force, db), + Self::Login(l) => l.run(settings), + Self::Register(r) => register::run( + settings, + r.username.as_str(), + r.email.as_str(), + r.password.as_str(), + ), + Self::Key => { + let key = std::fs::read(settings.local.key_path.as_str())?; + println!("{}", base64::encode(key)); + Ok(()) + } + Self::Uuid => { println!("{}", uuid_v4()); Ok(()) diff --git a/src/command/register.rs b/src/command/register.rs new file mode 100644 index 00000000..62bbeaeb --- /dev/null +++ b/src/command/register.rs @@ -0,0 +1,54 @@ +use std::collections::HashMap; +use std::fs::File; +use std::io::prelude::*; + +use eyre::{eyre, Result}; +use structopt::StructOpt; + +use crate::settings::Settings; + +#[derive(StructOpt)] +#[structopt(setting(structopt::clap::AppSettings::DeriveDisplayOrder))] +pub struct Cmd { + #[structopt(long, short)] + pub username: String, + + #[structopt(long, short)] + pub email: String, + + #[structopt(long, short)] + pub password: String, +} + +pub fn run(settings: &Settings, username: &str, email: &str, password: &str) -> Result<()> { + let mut map = HashMap::new(); + map.insert("username", username); + map.insert("email", email); + map.insert("password", password); + + let url = format!("{}/user/{}", settings.local.sync_address, username); + let resp = reqwest::blocking::get(url)?; + + if resp.status().is_success() { + println!("Username is already in use! Please try another."); + return Ok(()); + } + + let url = format!("{}/register", settings.local.sync_address); + let client = reqwest::blocking::Client::new(); + let resp = client.post(url).json(&map).send()?; + + if !resp.status().is_success() { + println!("Failed to register user - please check your details and try again"); + return Err(eyre!("failed to register user")); + } + + let session = resp.json::>()?; + let session = session["session"].clone(); + + let path = settings.local.session_path.as_str(); + let mut file = File::create(path)?; + file.write_all(session.as_bytes())?; + + Ok(()) +} diff --git a/src/command/search.rs b/src/command/search.rs index d51e29ef..b9f3987c 100644 --- a/src/command/search.rs +++ b/src/command/search.rs @@ -171,7 +171,8 @@ fn select_history(query: &[String], db: &mut impl Database) -> Result { .iter() .enumerate() .map(|(i, m)| { - let mut content = Span::raw(m.command.to_string()); + let mut content = + Span::raw(m.command.to_string().replace("\n", " ").replace("\t", " ")); if let Some(selected) = app.results_state.selected() { if selected == i { diff --git a/src/command/server.rs b/src/command/server.rs index 5156f409..ba2a9a2f 100644 --- a/src/command/server.rs +++ b/src/command/server.rs @@ -24,10 +24,10 @@ impl Cmd { match self { Self::Start { host, port } => { let host = host.as_ref().map_or( - settings.remote.host.clone(), + settings.server.host.clone(), std::string::ToString::to_string, ); - let port = port.map_or(settings.remote.port, |p| p); + let port = port.map_or(settings.server.port, |p| p); server::launch(settings, host, port); } diff --git a/src/command/sync.rs b/src/command/sync.rs new file mode 100644 index 00000000..facbe578 --- /dev/null +++ b/src/command/sync.rs @@ -0,0 +1,15 @@ +use eyre::Result; + +use crate::local::database::Database; +use crate::local::sync; +use crate::settings::Settings; + +pub fn run(settings: &Settings, force: bool, db: &mut impl Database) -> Result<()> { + sync::sync(settings, force, db)?; + println!( + "Sync complete! {} items in database, force: {}", + db.history_count()?, + force + ); + Ok(()) +} diff --git a/src/local/api_client.rs b/src/local/api_client.rs new file mode 100644 index 00000000..434c07ba --- /dev/null +++ b/src/local/api_client.rs @@ -0,0 +1,94 @@ +use chrono::Utc; +use eyre::Result; +use reqwest::header::AUTHORIZATION; + +use crate::api::{AddHistoryRequest, CountResponse, ListHistoryResponse}; +use crate::local::encryption::{decrypt, load_key}; +use crate::local::history::History; +use crate::settings::Settings; +use crate::utils::hash_str; + +pub struct Client<'a> { + settings: &'a Settings, +} + +impl<'a> Client<'a> { + pub const fn new(settings: &'a Settings) -> Self { + Client { settings } + } + + pub fn count(&self) -> Result { + let url = format!("{}/sync/count", self.settings.local.sync_address); + let client = reqwest::blocking::Client::new(); + + let resp = client + .get(url) + .header( + AUTHORIZATION, + format!("Token {}", self.settings.local.session_token), + ) + .send()?; + + let count = resp.json::()?; + + Ok(count.count) + } + + pub fn get_history( + &self, + sync_ts: chrono::DateTime, + history_ts: chrono::DateTime, + host: Option, + ) -> Result> { + let key = load_key(self.settings)?; + + let host = match host { + None => hash_str(&format!("{}:{}", whoami::hostname(), whoami::username())), + Some(h) => h, + }; + + // this allows for syncing between users on the same machine + let url = format!( + "{}/sync/history?sync_ts={}&history_ts={}&host={}", + self.settings.local.sync_address, + sync_ts.to_rfc3339(), + history_ts.to_rfc3339(), + host, + ); + let client = reqwest::blocking::Client::new(); + + let resp = client + .get(url) + .header( + AUTHORIZATION, + format!("Token {}", self.settings.local.session_token), + ) + .send()?; + + let history = resp.json::()?; + let history = history + .history + .iter() + .map(|h| serde_json::from_str(h).expect("invalid base64")) + .map(|h| decrypt(&h, &key).expect("failed to decrypt history! check your key")) + .collect(); + + Ok(history) + } + + pub fn post_history(&self, history: &[AddHistoryRequest]) -> Result<()> { + let client = reqwest::blocking::Client::new(); + + let url = format!("{}/history", self.settings.local.sync_address); + client + .post(url) + .json(history) + .header( + AUTHORIZATION, + format!("Token {}", self.settings.local.session_token), + ) + .send()?; + + Ok(()) + } +} diff --git a/src/local/database.rs b/src/local/database.rs index ad7078e5..977f11cc 100644 --- a/src/local/database.rs +++ b/src/local/database.rs @@ -1,3 +1,4 @@ +use chrono::prelude::*; use chrono::Utc; use std::path::Path; @@ -21,6 +22,10 @@ pub trait Database { fn update(&self, h: &History) -> Result<()>; fn history_count(&self) -> Result; + fn first(&self) -> Result; + fn last(&self) -> Result; + fn before(&self, timestamp: chrono::DateTime, count: i64) -> Result>; + fn prefix_search(&self, query: &str) -> Result>; } @@ -44,9 +49,7 @@ impl Sqlite { let conn = Connection::open(path)?; - if create { - Self::setup_db(&conn)?; - } + Self::setup_db(&conn)?; Ok(Self { conn }) } @@ -70,6 +73,14 @@ impl Sqlite { [], )?; + conn.execute( + "create table if not exists history_encrypted ( + id text primary key, + data blob not null + )", + [], + )?; + Ok(()) } @@ -87,7 +98,7 @@ impl Sqlite { ) values (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)", params![ h.id, - h.timestamp, + h.timestamp.timestamp_nanos(), h.duration, h.exit, h.command, @@ -146,7 +157,7 @@ impl Database for Sqlite { "update history set timestamp = ?2, duration = ?3, exit = ?4, command = ?5, cwd = ?6, session = ?7, hostname = ?8 where id = ?1", - params![h.id, h.timestamp, h.duration, h.exit, h.command, h.cwd, h.session, h.hostname], + params![h.id, h.timestamp.timestamp_nanos(), h.duration, h.exit, h.command, h.cwd, h.session, h.hostname], )?; Ok(()) @@ -183,6 +194,38 @@ impl Database for Sqlite { Ok(history_iter.filter_map(Result::ok).collect()) } + fn first(&self) -> Result { + let mut stmt = self + .conn + .prepare("SELECT * FROM history order by timestamp asc limit 1")?; + + let history = stmt.query_row(params![], |row| history_from_sqlite_row(None, row))?; + + Ok(history) + } + + fn last(&self) -> Result { + let mut stmt = self + .conn + .prepare("SELECT * FROM history order by timestamp desc limit 1")?; + + let history = stmt.query_row(params![], |row| history_from_sqlite_row(None, row))?; + + Ok(history) + } + + fn before(&self, timestamp: chrono::DateTime, count: i64) -> Result> { + let mut stmt = self.conn.prepare( + "SELECT * FROM history where timestamp <= ? order by timestamp desc limit ?", + )?; + + let history_iter = stmt.query_map(params![timestamp.timestamp_nanos(), count], |row| { + history_from_sqlite_row(None, row) + })?; + + Ok(history_iter.filter_map(Result::ok).collect()) + } + fn query(&self, query: &str, params: impl Params) -> Result> { let mut stmt = self.conn.prepare(query)?; @@ -218,7 +261,7 @@ fn history_from_sqlite_row( Ok(History { id, - timestamp: row.get(1)?, + timestamp: Utc.timestamp_nanos(row.get(1)?), duration: row.get(2)?, exit: row.get(3)?, command: row.get(4)?, diff --git a/src/local/encryption.rs b/src/local/encryption.rs new file mode 100644 index 00000000..3c1699e3 --- /dev/null +++ b/src/local/encryption.rs @@ -0,0 +1,108 @@ +// The general idea is that we NEVER send cleartext history to the server +// This way the odds of anything private ending up where it should not are +// very low +// The server authenticates via the usual username and password. This has +// nothing to do with the encryption, and is purely authentication! The client +// generates its own secret key, and encrypts all shell history with libsodium's +// secretbox. The data is then sent to the server, where it is stored. All +// clients must share the secret in order to be able to sync, as it is needed +// to decrypt + +use std::fs::File; +use std::io::prelude::*; +use std::path::PathBuf; + +use eyre::{eyre, Result}; +use sodiumoxide::crypto::secretbox; + +use crate::local::history::History; +use crate::settings::Settings; + +#[derive(Debug, Serialize, Deserialize)] +pub struct EncryptedHistory { + pub ciphertext: Vec, + pub nonce: secretbox::Nonce, +} + +// Loads the secret key, will create + save if it doesn't exist +pub fn load_key(settings: &Settings) -> Result { + let path = settings.local.key_path.as_str(); + + if PathBuf::from(path).exists() { + let bytes = std::fs::read(path)?; + let key: secretbox::Key = rmp_serde::from_read_ref(&bytes)?; + Ok(key) + } else { + let key = secretbox::gen_key(); + let buf = rmp_serde::to_vec(&key)?; + + let mut file = File::create(path)?; + file.write_all(&buf)?; + + Ok(key) + } +} + +pub fn encrypt(history: &History, key: &secretbox::Key) -> Result { + // serialize with msgpack + let buf = rmp_serde::to_vec(history)?; + + let nonce = secretbox::gen_nonce(); + + let ciphertext = secretbox::seal(&buf, &nonce, key); + + Ok(EncryptedHistory { ciphertext, nonce }) +} + +pub fn decrypt(encrypted_history: &EncryptedHistory, key: &secretbox::Key) -> Result { + let plaintext = secretbox::open(&encrypted_history.ciphertext, &encrypted_history.nonce, key) + .map_err(|_| eyre!("failed to open secretbox - invalid key?"))?; + + let history = rmp_serde::from_read_ref(&plaintext)?; + + Ok(history) +} + +#[cfg(test)] +mod test { + use sodiumoxide::crypto::secretbox; + + use crate::local::history::History; + + use super::{decrypt, encrypt}; + + #[test] + fn test_encrypt_decrypt() { + let key1 = secretbox::gen_key(); + let key2 = secretbox::gen_key(); + + let history = History::new( + chrono::Utc::now(), + "ls".to_string(), + "/home/ellie".to_string(), + 0, + 1, + Some("beep boop".to_string()), + Some("booop".to_string()), + ); + + let e1 = encrypt(&history, &key1).unwrap(); + let e2 = encrypt(&history, &key2).unwrap(); + + assert_ne!(e1.ciphertext, e2.ciphertext); + assert_ne!(e1.nonce, e2.nonce); + + // test decryption works + // this should pass + match decrypt(&e1, &key1) { + Err(e) => assert!(false, "failed to decrypt, got {}", e), + Ok(h) => assert_eq!(h, history), + }; + + // this should err + match decrypt(&e2, &key1) { + Ok(_) => assert!(false, "expected an error decrypting with invalid key"), + Err(_) => {} + }; + } +} diff --git a/src/local/history.rs b/src/local/history.rs index 0ca112bd..1712f8b9 100644 --- a/src/local/history.rs +++ b/src/local/history.rs @@ -1,12 +1,15 @@ use std::env; use std::hash::{Hash, Hasher}; +use chrono::Utc; + use crate::command::uuid_v4; -#[derive(Debug, Clon