Initial version

feature/database-encription
German Lashevich 6 years ago
commit 8a8383cee3

3
.gitignore vendored

@ -0,0 +1,3 @@
/target/
**/*.rs.bk
.history

380
Cargo.lock generated

@ -0,0 +1,380 @@
[[package]]
name = "ansi_term"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "atty"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)",
"termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "base32"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "bitflags"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "byte-tools"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "clap"
version = "2.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"ansi_term 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)",
"atty 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"strsim 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
"textwrap 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
"vec_map 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "constant_time_eq"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "crypto-mac"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"constant_time_eq 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
"generic-array 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "digest"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"generic-array 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "digest-buffer"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"byte-tools 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
"generic-array 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "dtoa"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "fake-simd"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "fuchsia-zircon"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"fuchsia-zircon-sys 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "fuchsia-zircon-sys"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "generic-array"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
"typenum 1.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "hmac"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"crypto-mac 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"digest 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
"generic-array 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "itoa"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "kernel32-sys"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "libc"
version = "0.2.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "nodrop"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "num-traits"
version = "0.1.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "oath"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"digest 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
"hmac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc-hex 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"sha-1 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
"sha2 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "quote"
version = "0.3.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "rand"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"fuchsia-zircon 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "redox_syscall"
version = "0.1.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "redox_termios"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"redox_syscall 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rustc-hex"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "rustotpony"
version = "0.1.0"
dependencies = [
"base32 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"clap 2.29.0 (registry+https://github.com/rust-lang/crates.io-index)",
"oath 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.24 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.24 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "serde"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "serde_derive"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive_internals 0.18.0 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "serde_derive_internals"
version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)",
"synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "serde_json"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"dtoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
"itoa 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.24 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "sha-1"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"byte-tools 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
"digest 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
"digest-buffer 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"generic-array 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "sha2"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"byte-tools 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
"digest 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
"digest-buffer 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"generic-array 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "strsim"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "syn"
version = "0.11.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
"synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "synom"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "termion"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)",
"redox_syscall 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)",
"redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "textwrap"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "typenum"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "unicode-width"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "unicode-xid"
version = "0.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "vec_map"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "winapi"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "winapi-build"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[metadata]
"checksum ansi_term 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6b3568b48b7cefa6b8ce125f9bb4989e52fbcc29ebea88df04cc7c5f12f70455"
"checksum atty 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "21e50800ec991574876040fff8ee46b136a53e985286fbe6a3bdfe6421b78860"
"checksum base32 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1b9605ba46d61df0410d8ac686b0007add8172eba90e8e909c347856fe794d8c"
"checksum bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b3c30d3802dfb7281680d6285f2ccdaa8c2d8fee41f93805dba5c4cf50dc23cf"
"checksum byte-tools 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "0919189ba800c7ffe8778278116b7e0de3905ab81c72abb69c85cbfef7991279"
"checksum clap 2.29.0 (registry+https://github.com/rust-lang/crates.io-index)" = "110d43e343eb29f4f51c1db31beb879d546db27998577e5715270a54bcf41d3f"
"checksum constant_time_eq 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8ff012e225ce166d4422e0e78419d901719760f62ae2b7969ca6b564d1b54a9e"
"checksum crypto-mac 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "dba62c86c26dcba13c278afcaac0c7452486fe604a2668a0dfa4e0edc98d8a9e"
"checksum digest 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7a68d759d7a66a4f63d5bd2a2b14ad7e8cf93fe8c9be227031cd4e72ab0e9ee8"
"checksum digest-buffer 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4eb92364e9f6d3da159257250532d448b218406d2acb149f724e8f48e9f5cb9a"
"checksum dtoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "09c3753c3db574d215cba4ea76018483895d7bff25a31b49ba45db21c48e50ab"
"checksum fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
"checksum fuchsia-zircon 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bd510087c325af53ba24f3be8f1c081b0982319adcb8b03cad764512923ccc19"
"checksum fuchsia-zircon-sys 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "08b3a6f13ad6b96572b53ce7af74543132f1a7055ccceb6d073dd36c54481859"
"checksum generic-array 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "dbe043cf9b85297937897087de81f590361686e1ac2d4d471b45435de5dfb6a6"
"checksum hmac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bdb5aa9647ba4711e9d6968dc1c810cd23989ed435443ca962e1bf6d8b8b83ff"
"checksum itoa 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8324a32baf01e2ae060e9de58ed0bc2320c9a2833491ee36cd3b4c414de4db8c"
"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
"checksum libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)" = "36fbc8a8929c632868295d0178dd8f63fc423fd7537ad0738372bd010b3ac9b0"
"checksum nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "9a2228dca57108069a5262f2ed8bd2e82496d2e074a06d1ccc7ce1687b6ae0a2"
"checksum num-traits 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "cacfcab5eb48250ee7d0c7896b51a2c5eec99c1feea5f32025635f5ae4b00070"
"checksum oath 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ec6405dc6afe8219020d535f9ad888a12b191bbc8ce1c55f7ee663bde5be80ca"
"checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a"
"checksum rand 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9d5f78082e6a6d042862611e9640cf20776185fee506cf6cf67e93c6225cee31"
"checksum redox_syscall 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)" = "ab105df655884ede59d45b7070c8a65002d921461ee813a024558ca16030eea0"
"checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76"
"checksum rustc-hex 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0ceb8ce7a5e520de349e1fa172baeba4a9e8d5ef06c47471863530bc4972ee1e"
"checksum serde 1.0.24 (registry+https://github.com/rust-lang/crates.io-index)" = "1c57ab4ec5fa85d08aaf8ed9245899d9bbdd66768945b21113b84d5f595cb6a1"
"checksum serde_derive 1.0.24 (registry+https://github.com/rust-lang/crates.io-index)" = "02c92ea07b6e49b959c1481804ebc9bfd92d3c459f1274c9a9546829e42a66ce"
"checksum serde_derive_internals 0.18.0 (registry+https://github.com/rust-lang/crates.io-index)" = "75c6aac7b99801a16db5b40b7bf0d7e4ba16e76fbf231e32a4677f271cac0603"
"checksum serde_json 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7cf5b0b5b4bd22eeecb7e01ac2e1225c7ef5e4272b79ee28a8392a8c8489c839"
"checksum sha-1 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8347606816471548cd60f0abd5ef0d513a81f5202dbdab9c09f17a15b5248484"
"checksum sha2 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "84920f9ac881e94e33ec89e1b3dcd36040523a308a92548e01217ce35d8cf6a8"
"checksum strsim 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b4d15c810519a91cf877e7e36e63fe068815c678181439f2f29e2562147c3694"
"checksum syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad"
"checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6"
"checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096"
"checksum textwrap 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c0b59b6b4b44d867f1370ef1bd91bfb262bf07bf0ae65c202ea2fbc16153b693"
"checksum typenum 1.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "13a99dc6780ef33c78780b826cf9d2a78840b72cae9474de4bcaf9051e60ebbd"
"checksum unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "bf3a113775714a22dcb774d8ea3655c53a32debae63a063acc00a91cc586245f"
"checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc"
"checksum vec_map 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "887b5b631c2ad01628bbbaa7dd4c869f80d3186688f8d0b6f58774fbe324988c"
"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"

@ -0,0 +1,13 @@
[package]
authors = ["German Lashevich <german.lashevich@gmail.com>"]
name = "rustotpony"
version = "0.1.0"
[dependencies]
base32 = "0.3.1"
clap = "^2.29.0"
oath = "0.10.2"
rand = "0.4.1"
serde = "1.0.24"
serde_derive = "1.0.24"
serde_json = "1.0.8"

@ -0,0 +1,32 @@
# RusTOTPony
## Usage
Base commads (simple CRUD):
- show dashboard
- show all apps
- with codes
- without codes
- show code for app
- add app (title, subtitle, key)
- rename app
- delete app
- clear database
```s
$ totp dash
$ totp list
$ totp show-all
$ totp show APPNAME
$ totp add APPNAME KEY # ask for key interactively?
$ totp delete APPNAME # with confirmation
$ totp rename APPNAME NEWNAME
$ totp eradicate # with non-trivial confirmation
```
## TODO
- completion
- encription with (manual or with keychain)
- password caching

@ -0,0 +1,451 @@
extern crate base32;
extern crate clap;
extern crate oath;
extern crate rand;
extern crate serde_json;
#[macro_use]
extern crate serde_derive;
use clap::{App, Arg, SubCommand};
use std::collections::HashMap;
use std::fs::{create_dir_all, File, OpenOptions};
use std::io::ErrorKind;
use std::path::{Path, PathBuf};
use std::thread;
use std::time::Duration;
const CONFIG_PATH: &str = ".rustotpony/db.json";
const DATABASE_VERSION: u8 = 1;
struct RusTOTPony<DB: Database> {
database: DB,
applications: HashMap<String, GenApp>,
}
impl<DB: Database> RusTOTPony<DB> {
fn new(db: DB) -> RusTOTPony<DB> {
RusTOTPony {
applications: db.get_applications(),
database: db,
}
}
fn add_application(&mut self, name: &str, secret: &str) -> Result<(), String> {
if let Some(secret_bytes) = GenApp::base32_to_bytes(secret) {
let new_app = GenApp::new(name, secret, secret_bytes);
if self.applications.contains_key(name) {
Err(format!("Application with name '{}' already exists!", name))
} else {
&self.applications.insert(String::from(name), new_app);
Ok(())
}
} else {
return Err(String::from("Couldn't decode secret key"))
}
}
fn delete_application(&mut self, name: &str) -> Result<(), String> {
if let Some(_) = self.applications.remove(name) {
Ok(())
} else {
Err(format!(
"Application with the name '{}' doesn't exist",
name
))
}
}
fn rename_application(&mut self, name: &str, newname: &str) -> Result<(), String> {
if let Some(app) = self.applications.get_mut(name) {
app.name = String::from(newname);
Ok(())
} else {
Err(format!("Application '{}' wasn't found", name))
}
}
fn get_applications(&self) -> Result<&HashMap<String, GenApp>, String> {
if self.applications.len() == 0 {
Err(String::from("There are no applications"))
} else {
Ok(&self.applications)
}
}
fn get_application(&self, name:&str) -> Result<&GenApp, String> {
if let Some(app) = self.applications.get(name) {
Ok(app)
} else {
Err(format!("Application '{}' wasn't found", name))
}
}
fn delete_all_applications(&mut self) {
self.applications = HashMap::new();
}
fn flush(&self) {
&self.database.save_applications(&self.applications);
}
}
trait Database {
fn get_applications(&self) -> HashMap<String, GenApp>;
fn save_applications(&self, applications: &HashMap<String, GenApp>);
}
impl Database for JsonDatabase {
fn get_applications(&self) -> HashMap<String, GenApp> {
let db_content = self.read_database_file();
db_content.content.applications
}
fn save_applications(&self, applications: &HashMap<String, GenApp>) {
let mut db_content = Self::get_empty_schema();
db_content.content.applications = applications.clone();
self.save_database_file(db_content);
}
}
#[derive(Serialize, Deserialize)]
struct JsonDatabaseSchema {
version: u8,
content: DatabaseContentSchema,
}
#[derive(Serialize, Deserialize)]
struct DatabaseContentSchema {
applications: HashMap<String, GenApp>,
}
struct JsonDatabase {
file_path: PathBuf,
}
impl JsonDatabase {
fn new(path: PathBuf) -> JsonDatabase {
JsonDatabase { file_path: path }
}
fn read_database_file(&self) -> JsonDatabaseSchema {
let file = match File::open(&self.file_path) {
Ok(f) => f,
Err(ref err) if err.kind() == ErrorKind::NotFound => return Self::get_empty_schema(),
Err(err) => panic!("There was a problem opening file: {:?}", err),
};
serde_json::from_reader(file).expect("Couldn't parse JSON from database file")
}
fn save_database_file(&self, content: JsonDatabaseSchema) {
let file = match self.open_database_file_for_write() {
Ok(f) => f,
Err(ref err) if err.kind() == ErrorKind::NotFound => self.create_database_file()
.expect("Couldn't create database file"),
Err(err) => panic!("Couldn't open database file: {:?}", err),
};
serde_json::to_writer(file, &content).expect("Couldn't write JSON data to database file");
}
fn create_database_file(&self) -> Result<File, std::io::Error> {
let dir = std::env::home_dir().unwrap_or(PathBuf::from("."));
if let Some(parent_dir) = Path::new(&self.file_path).parent() {
let dir = dir.join(parent_dir);
create_dir_all(dir)?;
}
self.open_database_file_for_write()
}
fn open_database_file_for_write(&self) -> Result<File, std::io::Error> {
OpenOptions::new()
.write(true)
.truncate(true)
.create(true)
.open(&self.file_path)
}
fn get_empty_schema() -> JsonDatabaseSchema {
JsonDatabaseSchema {
version: DATABASE_VERSION,
content: DatabaseContentSchema {
applications: HashMap::new(),
},
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
struct GenApp {
name: String,
secret: String,
username: String,
secret_bytes: Vec<u8>,
}
impl GenApp {
fn new(name: &str, secret: &str, secret_bytes: Vec<u8>) -> Self {
GenApp {
name: String::from(name),
secret: String::from(secret),
username: String::from(""),
secret_bytes: secret_bytes,
}
}
fn get_code(&self) -> u64 {
Self::totp(&self.secret_bytes)
}
fn base32_to_bytes(secret:&str) -> Option<Vec<u8>> {
base32::decode(base32::Alphabet::RFC4648 {padding: false}, secret)
}
fn totp(secret_bytes: &[u8]) -> u64 {
oath::totp_raw_now(&secret_bytes, 6, 0, 30, &oath::HashType::SHA1)
}
}
struct Cli {
app: RusTOTPony<JsonDatabase>,
}
impl Cli {
fn new() -> Self {
let db = JsonDatabase::new(Self::get_database_path());
Self {
app: RusTOTPony::new(db),
}
}
fn run(&mut self) {
match self.get_cli_api_matches().subcommand() {
("dash", Some(_)) => {
self.show_dashboard();
}
("list", Some(_)) => {
self.show_applications_list(false);
}
("show-all", Some(_)) => {
self.show_applications_list(true);
}
("show", Some(sub_app)) => {
let app_name: &str = sub_app
.value_of("APPNAME")
.expect("Couldn't read APPNAME for 'show' command");
self.show_application(app_name);
}
("add", Some(sub_app)) => {
let app_name: &str = sub_app
.value_of("APPNAME")
.expect("Couldn't read APPNAME for 'add' command");
let key: &str = sub_app
.value_of("KEY")
.expect("Couldn't read KEY for 'add' command");
self.add_application(app_name, key);
}
("delete", Some(sub_app)) => {
let app_name: &str = sub_app
.value_of("APPNAME")
.expect("Couldn't read APPNAME for 'delete' command");
self.delete_application(app_name);
}
("rename", Some(sub_app)) => {
let app_name: &str = sub_app
.value_of("APPNAME")
.expect("Couldn't read APPNAME for 'rename' command");
let new_name: &str = sub_app
.value_of("NEWNAME")
.expect("Couldn't read NEWNAME for 'rename' command");
self.rename_application(app_name, new_name);
}
("eradicate", Some(_)) => {
self.eradicate_database();
}
_ => {
self.show_applications_list(true);
}
}
}
fn get_cli_api_matches(&self) -> clap::ArgMatches<'static> {
App::new("Main")
.version("0.1.0")
.author("German Lashevich <german.lashevich@gmail.com>")
.about("TOTP code generator written with Rust")
.subcommand(
SubCommand::with_name("dash").about("Shows realtime dashboard with all generators"),
)
.subcommand(SubCommand::with_name("list").about("Lists all generators"))
.subcommand(
SubCommand::with_name("show-all")
.about("Shows all generators with their's current values"),
)
.subcommand(
SubCommand::with_name("show")
.about("Shows generator with it's current value")
.arg(Arg::with_name("APPNAME").required(true)),
)
.subcommand(
SubCommand::with_name("add")
.about("Adds new generator")
.arg(Arg::with_name("APPNAME").required(true))
.arg(Arg::with_name("KEY").required(true)),
)
.subcommand(
SubCommand::with_name("delete")
.about("Deletes generator")
.arg(Arg::with_name("APPNAME").required(true)),
)
.subcommand(
SubCommand::with_name("rename")
.about("Renames generator")
.arg(Arg::with_name("APPNAME").required(true))
.arg(Arg::with_name("NEWNAME").required(true)),
)
.subcommand(SubCommand::with_name("eradicate").about("Deletes all generators"))
.get_matches()
}
fn get_database_path() -> PathBuf {
let home = std::env::home_dir().unwrap_or(PathBuf::from("."));
home.join(Path::new(CONFIG_PATH))
}
fn show_dashboard(&self) {
match self.app.get_applications(){
Ok(apps) => {
let mut is_first_iteration = true;
let lines_count = apps.len();
loop {
if is_first_iteration {
is_first_iteration = false;
} else {
print!("\x1B[{}A", lines_count);
}
for (_, app) in apps {
println!{"{} {}", app.get_code(), app.name};
}
thread::sleep(Duration::from_millis(1000));
}
},
Err(err) => println!("{}", err),
}
}
fn show_applications_list(&self, _: bool) {
// TODO Create Table structure with HashMap as follows and metadata about columns - width, titles, names
let mut output_table: HashMap<&str, Vec<String>> = HashMap::new();
let mut applications_count = 0;
let apps = match self.app.get_applications() {
Ok(v) => v,
Err(e) => {
println!("{}", e);
return;
},
};
for (_, application) in apps {
applications_count += 1;
output_table
.entry("name")
.or_insert(Vec::new())
.push(application.name.clone());
output_table
.entry("key")
.or_insert(Vec::new())
.push(application.secret.clone());
output_table
.entry("username")
.or_insert(Vec::new())
.push(application.username.clone());
}
let name_max_length = output_table["name"]
.iter()
.fold("name".len(), |max, val| std::cmp::max(max, val.len()));
let key_max_length = output_table["key"]
.iter()
.fold("key".len(), |max, val| std::cmp::max(max, val.len()));
let username_max_length = output_table["username"]
.iter()
.fold("username".len(), |max, val| std::cmp::max(max, val.len()));
let header_row_delimiter = format!(
"+-{}-+-{}-+-{}-+",
"-".repeat(name_max_length),
"-".repeat(key_max_length),
"-".repeat(username_max_length)
);
println!("{}", header_row_delimiter);
println!(
"| {name:<name_width$} | {key: <key_width$} | {username: <username_width$} |",
name_width=name_max_length,
name="name",
key_width=key_max_length,
key="key",
username_width=username_max_length,
username="username"
);
println!("{}", header_row_delimiter);
for i in 0..applications_count {
let name = &output_table["name"][i];
let key = &output_table["key"][i];
let username = &output_table["username"][i];
println!(
"| {name:<name_width$} | {key: <key_width$} | {username: <username_width$} |",
name_width=name_max_length,
name=name,
key_width=key_max_length,
key=key,
username_width=username_max_length,
username=username
);
}
println!("{}", header_row_delimiter);
}
fn show_application(&self, name: &str) {
println!("{:?}", self.app.get_application(name));
}
fn add_application(&mut self, name: &str, key: &str) {
match self.app.add_application(name, key) {
Ok(_) => {
self.app.flush();
println!("New application created: {}", name)
},
Err(err) => println!("{} Aborting…", err),
}
}
fn delete_application(&mut self, name: &str) {
match self.app.delete_application(name) {
Ok(_) => {
self.app.flush();
println!("Application '{}' successfully deleted", name)
},
Err(err) => println!("Couldn't delete application '{}': {}", name, err),
};
}
fn rename_application(&mut self, name: &str, newname: &str) {
match self.app.rename_application(name, newname) {
Ok(_) => {
self.app.flush();
println!("Application '{}' successfully renamed to '{}'", name, newname)
},
Err(err) => println!("Couldn't rename application '{}': {}", name, err),
};
}
fn eradicate_database(&mut self) {
self.app.delete_all_applications();
self.app.flush();
println!("Done.");
}
}
fn main() {
Cli::new().run();
}
Loading…
Cancel
Save