#![deny(missing_docs,
missing_debug_implementations, missing_copy_implementations,
trivial_casts, trivial_numeric_casts,
unsafe_code,
unstable_features,
unused_import_braces, unused_qualifications)]
use std::{fs, fmt, path::PathBuf, error};
extern crate dirs;
extern crate serde;
extern crate ureq;
extern crate pem_rfc7468;
extern crate ed25519_dalek;
extern crate hex;
use dirs::home_dir;
use ed25519_dalek::Signer;
#[derive(Debug, Clone, Copy)]
pub enum ErrorKinds {
IllegalOperation,
InvalidKey,
MissingHome,
ConfigurationInaccessible,
RequestFailed,
ResponseInvalid,
KeyInaccessible,
SignatureFailed,
AuthModeUnresolved
}
impl fmt::Display for ErrorKinds {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}", self)
}
}
#[derive(Debug, Clone)]
pub struct Error {
message: String,
kind: ErrorKinds
}
impl Error {
fn new(msg: &str, kind: ErrorKinds) -> Error {
Error {
message: msg.to_owned(),
kind: kind
}
}
pub fn kind(&self) -> ErrorKinds {
return self.kind;
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}. {}", self.kind, self.message)
}
}
impl error::Error for Error {}
#[derive(serde::Serialize, serde::Deserialize, Debug)]
pub struct ConfigEd25519Schema {
pub public: String,
pub private: String,
pub enabled: bool
}
#[derive(serde::Serialize, serde::Deserialize, Debug)]
pub struct ConfigSchema {
pub preferred_username: String,
pub email: String,
pub password: String,
pub ed25519: ConfigEd25519Schema
}
#[derive(serde::Serialize)]
struct Authentication {
mode: String,
answer: String
}
#[derive(serde::Deserialize)]
struct TokenResponse {
jwt: String
}
#[derive(serde::Deserialize)]
struct ErrorResponse {
error: String
}
pub struct Vigor {
pub config: ConfigSchema,
pub path: PathBuf
}
impl fmt::Debug for Vigor {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "path: \"{}\"", self.path.display().to_string())
}
}
#[derive(Debug, Copy, Clone)]
pub enum AuthMode {
Ed25519,
Password,
Auto
}
impl Vigor {
fn get_config_path() -> Result<PathBuf, Error> {
match home_dir() {
Some(mut home) => {
home.push(".vigor");
home.set_extension("conf");
Ok(home)
},
None => Err(Error::new("Failed to get user's home directory for Vigor configuration file.", ErrorKinds::MissingHome))
}
}
pub fn read(&mut self) -> Result<(), Error> {
match fs::read_to_string(&self.path) {
Ok(data) => {
let output: Result<ConfigSchema, serde_json::Error> = serde_json::from_str(&data);
match output {
Ok(config) => {
self.config = config;
Ok(())
},
Err(error) => Err(Error::new(&error.to_string(), ErrorKinds::ConfigurationInaccessible))
}
},
Err(error) => Err(Error::new(&error.to_string(), ErrorKinds::ConfigurationInaccessible))
}
}
pub fn write(&self) -> Result<(), Error> {
match fs::write(&self.path, serde_json::to_string(&self.config).unwrap()) {
Ok(_) => {
Ok(())
},
Err(error) => Err(Error::new(&error.to_string(), ErrorKinds::ConfigurationInaccessible))
}
}
pub fn init(&mut self) -> Result<(), Error> {
if !self.path.exists() {
match Vigor::write(self) {
Ok(_) => Ok(()),
Err(error) => Err(error)
}
} else {
match Vigor::read(self) {
Ok(_) => Ok(()),
Err(error) => Err(error)
}
}
}
pub fn new() -> Result<Vigor, Error> {
match Vigor::get_config_path() {
Ok(config_path) => {
Ok(Vigor {
config: ConfigSchema {
preferred_username: "nobody".to_owned(),
email: "nobody@localhost".to_owned(),
password: "hunter2".to_owned(), ed25519: ConfigEd25519Schema {
public: "/path/to/your/keys/vigor.pem.pub".to_owned(),
private: "/path/to/your/keys/vigor.pem".to_owned(),
enabled: false
}
},
path: config_path
})
},
Err(error) => Err(error)
}
}
fn host_finalize(&self, host: &str) -> String {
let mut url = PathBuf::from(host);
url.push(&self.config.preferred_username);
url.display().to_string()
}
fn process_request_response(response: Result<ureq::Response, ureq::Error>) -> Result<ureq::Response, Error> {
match response {
Ok(response) => Ok(response),
Err(ureq::Error::Status(code, response)) => {
match response.into_json::<ErrorResponse>() {
Ok(payload) => {
Err(Error::new(&format!("Code {}. {}", code.to_string(), payload.error), ErrorKinds::RequestFailed))
},
Err(error) => Err(Error::new(&format!("Code {}. Response invalid, unable to decode further details for cause of error. {}", code.to_string(), error.to_string()), ErrorKinds::ResponseInvalid))
}
}
Err(error) => Err(Error::new(&error.to_string(), ErrorKinds::RequestFailed))
}
}
fn form_account_payload(&self, share_email: bool, use_password: bool, use_ed25519: bool) -> Result<serde_json::Map<String, serde_json::Value>, Error> {
let mut payload = serde_json::Map::new();
if share_email {
payload.insert("email".to_owned(), serde_json::Value::String(self.config.email.to_owned()));
}
if use_password {
match Vigor::get_authentication_password(self) {
Ok(password) => {
payload.insert("password".to_owned(), serde_json::Value::String(password));
},
Err(error) => {
return Err(error);
}
}
}
if use_ed25519 {
match fs::read_to_string(&self.config.ed25519.public) {
Ok(data) => {
payload.insert("ed25519key".to_owned(), serde_json::Value::String(data));
}
Err(error) => {
return Err(Error::new(&error.to_string(), ErrorKinds::KeyInaccessible))
}
}
}
Ok(payload)
}
pub fn put(&self, host: &str, share_email: bool, use_password: bool, use_ed25519: bool) -> Result<(), Error> {
if !use_password && !use_ed25519 {
return Err(Error::new("At least one authentication method must exist on the new account.", ErrorKinds::IllegalOperation))
}
match Vigor::form_account_payload(self, share_email, use_password, use_ed25519) {
Ok(payload) => {
match Vigor::process_request_response(ureq::put(&Vigor::host_finalize(self, &host)).send_json(payload)) {
Ok(_) => Ok(()),
Err(error) => Err(error)
}
},
Err(error) => Err(error)
}
}
fn get_authentication_ed25519(&self) -> Result<String, Error> {
match fs::read_to_string(&self.config.ed25519.private) {
Ok(data) => {
match pem_rfc7468::decode_vec(data.as_bytes()) {
Ok(data) => {
let raw = data.1;
if raw.len() < 32 {
return Err(Error::new("Ed25519 private key is not at least 32 bytes.", ErrorKinds::InvalidKey));
}
let key_as_bytes = &raw[(raw.len() - 32)..]; match ed25519_dalek::SecretKey::from_bytes(&key_as_bytes) {
Ok(secret_key) => {
let public_key: ed25519_dalek::PublicKey = (&secret_key).into();
let keypair = ed25519_dalek::Keypair {public: public_key, secret: secret_key};
match keypair.try_sign("SIGNME".as_bytes()) {
Ok(signature) => {
Ok(hex::encode(signature.to_bytes()))
},
Err(error) => Err(Error::new(&error.to_string(), ErrorKinds::SignatureFailed))
}
},
Err(error) => Err(Error::new(&error.to_string(), ErrorKinds::InvalidKey))
}
},
Err(error) => Err(Error::new(&error.to_string(), ErrorKinds::InvalidKey))
}
}
Err(error) => Err(Error::new(&error.to_string(), ErrorKinds::KeyInaccessible))
}
}
fn get_authentication_password(&self) -> Result<String, Error> {
if self.config.password.is_empty() {
return Err(Error::new("Password cannot be of zero length.", ErrorKinds::IllegalOperation))
} else {
return Ok(self.config.password.to_owned())
}
}
fn form_authentication_ed25519(&self) -> Result<Authentication, Error> {
match Vigor::get_authentication_ed25519(self) {
Ok(answer) => {
Ok(Authentication {mode: "ed25519".to_owned(), answer: answer})
},
Err(error) => Err(error)
}
}
fn form_authentication_password(&self) -> Result<Authentication, Error> {
match Vigor::get_authentication_password(self) {
Ok(answer) => {
Ok(Authentication {mode: "password".to_owned(), answer: answer})
},
Err(error) => Err(error)
}
}
fn form_authentication(&self, mode: AuthMode) -> Result<Authentication, Error> {
match mode {
AuthMode::Ed25519 => {
Vigor::form_authentication_ed25519(self)
},
AuthMode::Password => {
Vigor::form_authentication_password(self)
},
AuthMode::Auto => {
if self.config.ed25519.enabled {
match Vigor::form_authentication_ed25519(self) {
Ok(payload) => {
return Ok(payload)
},
Err(_) => {}
};
}
match Vigor::form_authentication_password(self) {
Ok(payload) => {
return Ok(payload)
},
Err(_) => {
return Err(Error::new("No authentication modes available that aren't disabled or erroneous.", ErrorKinds::AuthModeUnresolved));
}
};
}
}
}
pub fn get(&self, host: &str, mode: AuthMode) -> Result<String, Error> {
match Vigor::form_authentication(self, mode) {
Ok(payload) => {
match Vigor::process_request_response(ureq::get(&Vigor::host_finalize(self, &host)).send_json(payload)) {
Ok(response) => {
match response.into_json::<TokenResponse>() {
Ok(payload) => Ok(payload.jwt),
Err(error) => Err(Error::new(&error.to_string(), ErrorKinds::ResponseInvalid))
}
},
Err(error) => Err(error)
}
},
Err(error) => Err(error)
}
}
pub fn delete(&self, host: &str, mode: AuthMode) -> Result<(), Error> {
match Vigor::form_authentication(self, mode) {
Ok(payload) => {
match Vigor::process_request_response(ureq::delete(&Vigor::host_finalize(self, &host)).send_json(payload)) {
Ok(_) => Ok(()),
Err(error) => Err(error)
}
},
Err(error) => Err(error)
}
}
pub fn patch(&self, host: &str, mode: AuthMode, share_email: bool, use_password: bool, use_ed25519: bool) -> Result<(), Error> {
if !share_email && !use_password && !use_ed25519 {
return Err(Error::new("At least one account property needs to be updated.", ErrorKinds::IllegalOperation));
}
match Vigor::form_authentication(self, mode) {
Ok(payload) => {
let mut payload_mod: serde_json::Map<String, serde_json::Value> = serde_json::to_value(payload).unwrap().as_object().unwrap().clone();
match Vigor::form_account_payload(self, share_email, use_password, use_ed25519) {
Ok(changes) => {
payload_mod.insert("new".to_string(), serde_json::Value::Object(changes));
match Vigor::process_request_response(ureq::patch(&Vigor::host_finalize(self, &host)).send_json(&payload_mod)) {
Ok(_) => Ok(()),
Err(error) => Err(error)
}
},
Err(error) => Err(error)
}
},
Err(error) => Err(error)
}
}
}