#![allow(non_snake_case)]
use crate::ported::errors::{self, field};
use crate::ported::request::common::normalizePasswordStorePath;
use crate::ported::request::process::request;
use crate::ported::response;
use serde::Serialize;
use std::process::Command;
#[derive(Serialize, Debug, Default)]
pub struct OtpResponse {
#[serde(rename = "code")]
pub Code: String,
}
pub fn otp(request: &request) {
if !request.File.ends_with(".gpg") {
response::SendErrorAndExit(
errors::Code::InvalidPasswordFileExtension,
Some(response::params_of(&[
(field::MESSAGE, "The requested password file does not have the expected '.gpg' extension"),
(field::ACTION, "otp"),
(field::FILE, &request.File),
])),
);
}
let store = match request.Settings.Stores.get(&request.StoreID) {
Some(s) => s.clone(),
None => {
response::SendErrorAndExit(
errors::Code::InvalidPasswordStore,
Some(response::params_of(&[
(field::MESSAGE, "The password store is not present in the list of stores"),
(field::ACTION, "otp"),
(field::STORE_ID, &request.StoreID),
])),
);
}
};
let normalized = match normalizePasswordStorePath(&store.Path) {
Ok(p) => p,
Err(e) => {
response::SendErrorAndExit(
errors::Code::InaccessiblePasswordStore,
Some(response::params_of(&[
(field::MESSAGE, "The password store is not accessible"),
(field::ACTION, "otp"),
(field::ERROR, &e),
(field::STORE_ID, &store.ID),
(field::STORE_NAME, &store.Name),
(field::STORE_PATH, &store.Path),
])),
);
}
};
let entry = request.File.trim_end_matches(".gpg");
let output = Command::new("pass")
.env("PASSWORD_STORE_DIR", &normalized)
.args(["otp", entry])
.output();
let output = match output {
Ok(o) => o,
Err(e) => {
response::SendErrorAndExit(
errors::Code::UnableToDecryptPasswordFile,
Some(response::params_of(&[
(field::MESSAGE, "Unable to spawn `pass otp`"),
(field::ACTION, "otp"),
(field::ERROR, &e.to_string()),
(field::FILE, &request.File),
(field::STORE_ID, &store.ID),
(field::STORE_NAME, &store.Name),
(field::STORE_PATH, &store.Path),
])),
);
}
};
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr).into_owned();
response::SendErrorAndExit(
errors::Code::UnableToDecryptPasswordFile,
Some(response::params_of(&[
(field::MESSAGE, "`pass otp` failed"),
(field::ACTION, "otp"),
(field::ERROR, stderr.trim()),
(field::FILE, &request.File),
(field::STORE_ID, &store.ID),
(field::STORE_NAME, &store.Name),
(field::STORE_PATH, &store.Path),
])),
);
}
let code = String::from_utf8_lossy(&output.stdout).trim().to_string();
response::SendOk(OtpResponse { Code: code });
}
pub fn extract_otpauth(body: &str) -> Option<String> {
body.lines()
.find(|l| l.starts_with("otpauth://"))
.map(|s| s.to_string())
}
#[allow(non_snake_case)]
const _: () = ();