mirror of
https://github.com/uqbar-dao/nectar.git
synced 2024-12-22 16:11:38 +03:00
reorganized register code to create keyfile on boot route
This commit is contained in:
parent
165746204a
commit
2784260c53
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -4826,6 +4826,7 @@ dependencies = [
|
||||
"anyhow",
|
||||
"async-recursion",
|
||||
"async-trait",
|
||||
"base64 0.13.1",
|
||||
"bincode",
|
||||
"blake3",
|
||||
"bytes",
|
||||
|
@ -16,6 +16,7 @@ aes-gcm = "0.10.2"
|
||||
anyhow = "1.0.71"
|
||||
async-recursion = "1.0.4"
|
||||
async-trait = "0.1.71"
|
||||
base64 = "0.13.0"
|
||||
bincode = "1.3.3"
|
||||
blake3 = "1.4.1"
|
||||
bytes = "1.4.0"
|
||||
|
@ -27,7 +27,6 @@ pub fn encode_keyfile(
|
||||
jwt: Vec<u8>,
|
||||
file_key: Vec<u8>,
|
||||
) -> Vec<u8> {
|
||||
println!("generating disk encryption keys...");
|
||||
let mut disk_key: DiskKey = [0u8; CREDENTIAL_LEN];
|
||||
|
||||
let rng = SystemRandom::new();
|
||||
|
201
src/main.rs
201
src/main.rs
@ -169,9 +169,7 @@ async fn main() {
|
||||
{
|
||||
ip
|
||||
} else {
|
||||
println!(
|
||||
"\x1b[38;5;196mfailed to find public IPv4 address: booting as a routed node\x1b[0m"
|
||||
);
|
||||
println!( "\x1b[38;5;196mfailed to find public IPv4 address: booting as a routed node\x1b[0m");
|
||||
std::net::Ipv4Addr::LOCALHOST
|
||||
}
|
||||
};
|
||||
@ -189,200 +187,35 @@ async fn main() {
|
||||
// that updates their PKI info on-chain.
|
||||
let http_server_port = http_server::find_open_port(8080).await.unwrap();
|
||||
let (kill_tx, kill_rx) = oneshot::channel::<bool>();
|
||||
let keyfile = fs::read(format!("{}/.keys", home_directory_path)).await;
|
||||
|
||||
let (our, networking_keypair, jwt_secret_bytes, file_key): (
|
||||
Identity,
|
||||
signature::Ed25519KeyPair,
|
||||
Vec<u8>,
|
||||
Vec<u8>,
|
||||
) = if keyfile.is_ok() {
|
||||
// LOGIN flow
|
||||
println!(
|
||||
"\u{1b}]8;;{}\u{1b}\\{}\u{1b}]8;;\u{1b}\\",
|
||||
format!("http://localhost:{}/login", http_server_port),
|
||||
"Click here to log in to your node.",
|
||||
);
|
||||
println!("(http://localhost:{}/login)", http_server_port);
|
||||
if our_ip != std::net::Ipv4Addr::LOCALHOST {
|
||||
println!(
|
||||
"(if on a remote machine: http://{}:{}/login)",
|
||||
our_ip, http_server_port
|
||||
);
|
||||
}
|
||||
|
||||
let (tx, mut rx) = mpsc::channel::<(
|
||||
String,
|
||||
Vec<String>,
|
||||
signature::Ed25519KeyPair,
|
||||
Vec<u8>,
|
||||
Vec<u8>,
|
||||
)>(1);
|
||||
let (username, routers, networking_keypair, jwt_secret_bytes, file_key) = tokio::select! {
|
||||
_ = register::login(
|
||||
tx,
|
||||
kill_rx,
|
||||
keyfile.unwrap(),
|
||||
http_server_port,
|
||||
) => panic!("login failed"),
|
||||
(username, routers, networking_keypair, jwt_secret_bytes, file_key) = async {
|
||||
while let Some(fin) = rx.recv().await {
|
||||
return fin
|
||||
}
|
||||
panic!("login failed")
|
||||
} => (username, routers, networking_keypair, jwt_secret_bytes, file_key),
|
||||
let disk_keyfile = match fs::read(format!("{}/.keys", home_directory_path)).await {
|
||||
Ok(keyfile) => keyfile,
|
||||
Err(_) => Vec::new(),
|
||||
};
|
||||
|
||||
// check if Identity for this username has correct networking keys,
|
||||
// if not, prompt user to reset them.
|
||||
let Ok(Ok(ws_rpc)) = timeout(
|
||||
tokio::time::Duration::from_secs(10),
|
||||
Provider::<Ws>::connect(rpc_url.clone()),
|
||||
)
|
||||
.await
|
||||
else {
|
||||
panic!("rpc: couldn't connect to blockchain wss endpoint. you MUST set an endpoint with --rpc flag, go to alchemy.com and get a free API key, then use the wss endpoint that looks like this: wss://eth-sepolia.g.alchemy.com/v2/<your-api-key>");
|
||||
};
|
||||
let Ok(Ok(_)) = timeout(
|
||||
tokio::time::Duration::from_secs(10),
|
||||
ws_rpc.get_block_number(),
|
||||
)
|
||||
.await
|
||||
else {
|
||||
panic!("error: RPC endpoint not responding, try setting one with --rpc flag");
|
||||
};
|
||||
let qns_address: EthAddress = QNS_SEPOLIA_ADDRESS.parse().unwrap();
|
||||
let contract = QNSRegistry::new(qns_address, ws_rpc.into());
|
||||
let node_id: U256 = namehash(&username).as_bytes().into();
|
||||
let Ok(onchain_id) = contract.ws(node_id).call().await else {
|
||||
panic!("error: RPC endpoint failed to fetch our node_id");
|
||||
};
|
||||
print_sender
|
||||
.send(Printout {
|
||||
verbosity: 0,
|
||||
content: "established connection to Sepolia RPC".to_string(),
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
// double check that routers match on-chain information
|
||||
let namehashed_routers: Vec<[u8; 32]> = routers
|
||||
.clone()
|
||||
.into_iter()
|
||||
.map(|name| {
|
||||
let hash = namehash(&name);
|
||||
let mut result = [0u8; 32];
|
||||
result.copy_from_slice(hash.as_bytes());
|
||||
result
|
||||
})
|
||||
.collect();
|
||||
|
||||
// double check that keys match on-chain information
|
||||
if onchain_id.routers != namehashed_routers
|
||||
|| onchain_id.public_key != networking_keypair.public_key().as_ref()
|
||||
|| (onchain_id.ip != 0
|
||||
&& onchain_id.ip != <std::net::Ipv4Addr as Into<u32>>::into(our_ip))
|
||||
{
|
||||
panic!("CRITICAL: your routing information does not match on-chain records");
|
||||
}
|
||||
|
||||
let our_identity = Identity {
|
||||
name: username.clone(),
|
||||
networking_key: format!(
|
||||
"0x{}",
|
||||
hex::encode(networking_keypair.public_key().as_ref())
|
||||
),
|
||||
ws_routing: if onchain_id.ip > 0 && onchain_id.port > 0 {
|
||||
let ip = format!(
|
||||
"{}.{}.{}.{}",
|
||||
(onchain_id.ip >> 24) & 0xFF,
|
||||
(onchain_id.ip >> 16) & 0xFF,
|
||||
(onchain_id.ip >> 8) & 0xFF,
|
||||
onchain_id.ip & 0xFF
|
||||
);
|
||||
Some((ip, onchain_id.port))
|
||||
} else {
|
||||
None
|
||||
},
|
||||
allowed_routers: routers,
|
||||
};
|
||||
|
||||
(
|
||||
our_identity.clone(),
|
||||
networking_keypair,
|
||||
jwt_secret_bytes,
|
||||
file_key,
|
||||
)
|
||||
} else {
|
||||
// REGISTER flow
|
||||
println!(
|
||||
"\u{1b}]8;;{}\u{1b}\\{}\u{1b}]8;;\u{1b}\\",
|
||||
format!("http://localhost:{}", http_server_port),
|
||||
"Click here to register your node.",
|
||||
);
|
||||
println!("(http://localhost:{})", http_server_port);
|
||||
if our_ip != std::net::Ipv4Addr::LOCALHOST {
|
||||
println!(
|
||||
"(if on a remote machine: http://{}:{})",
|
||||
our_ip, http_server_port
|
||||
);
|
||||
}
|
||||
|
||||
let (tx, mut rx) = mpsc::channel::<(Identity, String, Document, Vec<u8>)>(1);
|
||||
let (mut our, password, serialized_networking_keypair, jwt_secret_bytes) = tokio::select! {
|
||||
_ = register::register(tx, kill_rx, our_ip.to_string(), http_server_port, http_server_port)
|
||||
let (tx, mut rx) = mpsc::channel::<(Identity, Keyfile, Vec<u8>)>(1);
|
||||
let (mut our, decoded_keyfile, encoded_keyfile) = tokio::select! {
|
||||
_ = register::register(tx, kill_rx, our_ip.to_string(), http_server_port, disk_keyfile)
|
||||
=> panic!("registration failed"),
|
||||
(our, password, serialized_networking_keypair, jwt_secret_bytes) = async {
|
||||
while let Some(fin) = rx.recv().await {
|
||||
return fin
|
||||
}
|
||||
(our, decoded_keyfile, encoded_keyfile) = async {
|
||||
while let Some(fin) = rx.recv().await { return fin }
|
||||
panic!("registration failed")
|
||||
} => (our, password, serialized_networking_keypair, jwt_secret_bytes),
|
||||
} => (our, decoded_keyfile, encoded_keyfile),
|
||||
};
|
||||
|
||||
println!(
|
||||
"saving encrypted networking keys to {}/.keys",
|
||||
home_directory_path
|
||||
);
|
||||
println!("saving encrypted networking keys to {}/.keys", home_directory_path);
|
||||
|
||||
let networking_keypair =
|
||||
signature::Ed25519KeyPair::from_pkcs8(serialized_networking_keypair.as_ref()).unwrap();
|
||||
|
||||
// TODO fix register frontend so this isn't necessary
|
||||
our.networking_key = format!("0x{}", our.networking_key);
|
||||
|
||||
let file_key = keygen::generate_file_key();
|
||||
|
||||
fs::write(
|
||||
format!("{}/.keys", home_directory_path),
|
||||
keygen::encode_keyfile(
|
||||
password,
|
||||
our.name.clone(),
|
||||
our.allowed_routers.clone(),
|
||||
serialized_networking_keypair,
|
||||
jwt_secret_bytes.clone(),
|
||||
file_key.clone(),
|
||||
),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
fs::write(format!("{}/.keys", home_directory_path), encoded_keyfile)
|
||||
.await.unwrap();
|
||||
|
||||
println!("registration complete!");
|
||||
(
|
||||
our,
|
||||
networking_keypair,
|
||||
jwt_secret_bytes.to_vec(),
|
||||
file_key.to_vec(),
|
||||
)
|
||||
};
|
||||
|
||||
let (kernel_process_map, manifest, vfs_messages) = filesystem::load_fs(
|
||||
our.name.clone(),
|
||||
home_directory_path.clone(),
|
||||
file_key,
|
||||
decoded_keyfile.file_key,
|
||||
fs_config,
|
||||
)
|
||||
.await
|
||||
.expect("fs load failed!");
|
||||
).await.expect("fs load failed!");
|
||||
|
||||
let _ = kill_tx.send(true);
|
||||
let _ = print_sender
|
||||
@ -398,7 +231,7 @@ async fn main() {
|
||||
*
|
||||
* if any of these modules fail, the program exits with an error.
|
||||
*/
|
||||
let networking_keypair_arc = Arc::new(networking_keypair);
|
||||
let networking_keypair_arc = Arc::new(decoded_keyfile.networking_keypair);
|
||||
|
||||
let mut tasks = tokio::task::JoinSet::<Result<()>>::new();
|
||||
tasks.spawn(kernel::kernel(
|
||||
@ -443,7 +276,7 @@ async fn main() {
|
||||
tasks.spawn(http_server::http_server(
|
||||
our.name.clone(),
|
||||
http_server_port,
|
||||
jwt_secret_bytes.clone(),
|
||||
decoded_keyfile.jwt_secret_bytes.clone(),
|
||||
http_server_receiver,
|
||||
kernel_message_sender.clone(),
|
||||
print_sender.clone(),
|
||||
|
190
src/register.rs
190
src/register.rs
@ -1,4 +1,5 @@
|
||||
use aes_gcm::aead::KeyInit;
|
||||
use base64;
|
||||
use hmac::Hmac;
|
||||
use jwt::SignWithKey;
|
||||
use ring::pkcs8::Document;
|
||||
@ -8,7 +9,7 @@ use sha2::Sha256;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use tokio::sync::{mpsc, oneshot};
|
||||
use warp::{
|
||||
http::header::{HeaderValue, SET_COOKIE},
|
||||
http::{ StatusCode, header::{HeaderValue, SET_COOKIE}, },
|
||||
Filter, Rejection, Reply,
|
||||
};
|
||||
|
||||
@ -16,7 +17,7 @@ use crate::http_server;
|
||||
use crate::keygen;
|
||||
use crate::types::*;
|
||||
|
||||
type RegistrationSender = mpsc::Sender<(Identity, String, Document, Vec<u8>)>;
|
||||
type RegistrationSender = mpsc::Sender<(Identity, Keyfile, Vec<u8>)>;
|
||||
|
||||
pub fn generate_jwt(jwt_secret_bytes: &[u8], username: String) -> Option<String> {
|
||||
let jwt_secret: Hmac<Sha256> = match Hmac::new_from_slice(&jwt_secret_bytes) {
|
||||
@ -41,36 +42,47 @@ pub async fn register(
|
||||
kill_rx: oneshot::Receiver<bool>,
|
||||
ip: String,
|
||||
port: u16,
|
||||
redir_port: u16,
|
||||
keyfile: Vec<u8>
|
||||
) {
|
||||
let our = Arc::new(Mutex::new(None));
|
||||
let networking_keypair = Arc::new(Mutex::new(None));
|
||||
|
||||
let our_get = our.clone();
|
||||
let networking_keypair_post = networking_keypair.clone();
|
||||
let our_arc = Arc::new(Mutex::new(None));
|
||||
let our_ws_info = our_arc.clone();
|
||||
|
||||
let net_keypair_arc = Arc::new(Mutex::new(None));
|
||||
let net_keypair_ws_info = net_keypair_arc.clone();
|
||||
|
||||
let keyfile_arc = Arc::new(Mutex::new(Some(keyfile)));
|
||||
let keyfile_has = keyfile_arc.clone();
|
||||
|
||||
let static_files = warp::path("static").and(warp::fs::dir("./src/register/build/static/"));
|
||||
let react_app = warp::path::end()
|
||||
.and(warp::get())
|
||||
.and(warp::fs::file("./src/register/build/index.html"));
|
||||
|
||||
let api = warp::path("get-ws-info").and(
|
||||
// 1. Get uqname (already on chain) and return networking information
|
||||
warp::get()
|
||||
let api = warp::path("has-keyfile")
|
||||
.and(warp::get()
|
||||
.and(warp::any().map(move || keyfile_has.clone()))
|
||||
.and_then(handle_has_keyfile))
|
||||
.or(warp::path("info")
|
||||
.and(warp::get()
|
||||
.and(warp::any().map(move || ip.clone()))
|
||||
.and(warp::any().map(move || our_get.clone()))
|
||||
.and(warp::any().map(move || networking_keypair_post.clone()))
|
||||
.and_then(handle_get)
|
||||
// 2. trigger for finalizing registration once on-chain actions are done
|
||||
.or(warp::post()
|
||||
.and(warp::any().map(move || our_ws_info.clone()))
|
||||
.and(warp::any().map(move || net_keypair_ws_info.clone()))
|
||||
.and_then(handle_info)))
|
||||
.or(warp::path("vet-keyfile")
|
||||
.and(warp::post()
|
||||
.and(warp::body::content_length_limit(1024 * 16))
|
||||
.and(warp::body::json())
|
||||
.and_then(handle_keyfile_check)))
|
||||
.or(warp::path("boot")
|
||||
.and(warp::put()
|
||||
.and(warp::body::content_length_limit(1024 * 16))
|
||||
.and(warp::body::json())
|
||||
.and(warp::any().map(move || tx.clone()))
|
||||
.and(warp::any().map(move || our.lock().unwrap().take().unwrap()))
|
||||
.and(warp::any().map(move || networking_keypair.lock().unwrap().take().unwrap()))
|
||||
.and(warp::any().map(move || redir_port))
|
||||
.and_then(handle_post)),
|
||||
);
|
||||
.and(warp::any().map(move || our_arc.lock().unwrap().take().unwrap()))
|
||||
.and(warp::any().map(move || net_keypair_arc.lock().unwrap().take().unwrap()))
|
||||
.and(warp::any().map(move || keyfile_arc.lock().unwrap().take().unwrap()))
|
||||
.and_then(handle_boot)));
|
||||
|
||||
let routes = static_files.or(react_app).or(api);
|
||||
|
||||
@ -83,14 +95,130 @@ pub async fn register(
|
||||
.await;
|
||||
}
|
||||
|
||||
async fn handle_get(
|
||||
async fn handle_has_keyfile(
|
||||
keyfile: Arc<Mutex<Option<Vec<u8>>>>,
|
||||
) -> Result<impl Reply, Rejection> {
|
||||
|
||||
Ok(warp::reply::json(&keyfile.lock().unwrap().is_some()))
|
||||
|
||||
}
|
||||
|
||||
async fn handle_keyfile_check(
|
||||
payload: KeyfileCheck
|
||||
) -> Result<impl Reply, Rejection> {
|
||||
|
||||
let keyfile = base64::decode(payload.keyfile).unwrap();
|
||||
|
||||
match keygen::decode_keyfile(keyfile, &payload.password) {
|
||||
Ok(_) => Ok(warp::reply::with_status(warp::reply(), StatusCode::OK)),
|
||||
Err(_) => Err(warp::reject()),
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
async fn handle_keyfile_gen(
|
||||
payload: Registration,
|
||||
our: Arc<Mutex<Option<Identity>>>,
|
||||
networking_keypair: Arc<Mutex<Option<Document>>>,
|
||||
jwt_secret: Arc<Mutex<Option<Vec<u8>>>>,
|
||||
) -> Result<impl Reply, Rejection> {
|
||||
|
||||
Ok(warp::reply::with_status(warp::reply(), StatusCode::OK))
|
||||
|
||||
}
|
||||
|
||||
async fn handle_boot(
|
||||
info: BootInfo,
|
||||
sender: RegistrationSender,
|
||||
mut our: Identity,
|
||||
networking_keypair: Document,
|
||||
mut encoded_keyfile: Vec<u8>,
|
||||
) -> Result<impl Reply, Rejection> {
|
||||
|
||||
println!("hello");
|
||||
|
||||
if info.direct {
|
||||
our.allowed_routers = vec![];
|
||||
} else {
|
||||
our.ws_routing = None;
|
||||
}
|
||||
|
||||
println!("~~~~~");
|
||||
|
||||
if encoded_keyfile.is_empty() && !info.keyfile.is_empty() {
|
||||
match base64::decode(info.keyfile) {
|
||||
Ok(k) => encoded_keyfile = k,
|
||||
Err(_) => return Err(warp::reject()),
|
||||
}
|
||||
}
|
||||
|
||||
println!("_____");
|
||||
|
||||
let decoded_keyfile = if !encoded_keyfile.is_empty() {
|
||||
match keygen::decode_keyfile(encoded_keyfile.clone(), &info.password) {
|
||||
Ok(k) => k,
|
||||
Err(_) => return Err(warp::reject()),
|
||||
}
|
||||
} else {
|
||||
let seed = SystemRandom::new();
|
||||
let mut jwt_secret = [0u8, 32];
|
||||
ring::rand::SecureRandom::fill(&seed, &mut jwt_secret).unwrap();
|
||||
|
||||
let networking_pair = signature::Ed25519KeyPair::from_pkcs8(networking_keypair.as_ref()).unwrap();
|
||||
|
||||
Keyfile {
|
||||
username: our.name.clone(),
|
||||
routers: our.allowed_routers.clone(),
|
||||
networking_keypair: signature::Ed25519KeyPair
|
||||
::from_pkcs8(networking_keypair.as_ref()).unwrap(),
|
||||
jwt_secret_bytes: jwt_secret.to_vec(),
|
||||
file_key: keygen::generate_file_key(),
|
||||
}
|
||||
};
|
||||
|
||||
println!(">>>>>");
|
||||
|
||||
if encoded_keyfile.is_empty() {
|
||||
encoded_keyfile = keygen::encode_keyfile(
|
||||
info.password,
|
||||
decoded_keyfile.username.clone(),
|
||||
decoded_keyfile.routers.clone(),
|
||||
networking_keypair,
|
||||
decoded_keyfile.jwt_secret_bytes.clone(),
|
||||
decoded_keyfile.file_key.clone(),
|
||||
);
|
||||
}
|
||||
|
||||
println!("<<<<<");
|
||||
|
||||
let token = match generate_jwt(&decoded_keyfile.jwt_secret_bytes, our.name.clone()) {
|
||||
Some(token) => token,
|
||||
None => return Err(warp::reject()),
|
||||
};
|
||||
|
||||
sender.send((our.clone(), decoded_keyfile, encoded_keyfile.clone())).await.unwrap();
|
||||
|
||||
let mut response = warp::reply::html("Success".to_string()).into_response();
|
||||
|
||||
println!("ioioioio");
|
||||
|
||||
let headers = response.headers_mut();
|
||||
headers.append(SET_COOKIE, HeaderValue::from_str(
|
||||
&format!("uqbar-auth_{}={};", &our.name, &token)).unwrap());
|
||||
headers.append(SET_COOKIE, HeaderValue::from_str(
|
||||
&format!("uqbar-ws-auth_{}={};", &our.name, &token)).unwrap());
|
||||
|
||||
Ok(warp::reply::with_status(warp::reply(), StatusCode::OK))
|
||||
}
|
||||
|
||||
async fn handle_info(
|
||||
ip: String,
|
||||
our_get: Arc<Mutex<Option<Identity>>>,
|
||||
networking_keypair_post: Arc<Mutex<Option<Document>>>,
|
||||
our_arc: Arc<Mutex<Option<Identity>>>,
|
||||
networking_keypair_arc: Arc<Mutex<Option<Document>>>,
|
||||
) -> Result<impl Reply, Rejection> {
|
||||
// 1. Generate networking keys
|
||||
let (public_key, serialized_networking_keypair) = keygen::generate_networking_key();
|
||||
*networking_keypair_post.lock().unwrap() = Some(serialized_networking_keypair);
|
||||
*networking_keypair_arc.lock().unwrap() = Some(serialized_networking_keypair);
|
||||
|
||||
// 2. set our...
|
||||
// TODO: if IP is localhost, assign a router...
|
||||
@ -107,10 +235,10 @@ async fn handle_get(
|
||||
],
|
||||
};
|
||||
|
||||
*our_get.lock().unwrap() = Some(our.clone());
|
||||
*our_arc.lock().unwrap() = Some(our.clone());
|
||||
|
||||
// return response containing networking information
|
||||
Ok(warp::reply::json(&our))
|
||||
|
||||
}
|
||||
|
||||
async fn handle_post(
|
||||
@ -118,7 +246,7 @@ async fn handle_post(
|
||||
sender: RegistrationSender,
|
||||
mut our: Identity,
|
||||
networking_keypair: Document,
|
||||
_redir_port: u16,
|
||||
keyfile: Vec<u8>,
|
||||
) -> Result<impl Reply, Rejection> {
|
||||
if info.direct {
|
||||
our.allowed_routers = vec![];
|
||||
@ -139,10 +267,10 @@ async fn handle_post(
|
||||
let cookie_value = format!("uqbar-auth_{}={};", &our.name, &token);
|
||||
let ws_cookie_value = format!("uqbar-ws-auth_{}={};", &our.name, &token);
|
||||
|
||||
sender
|
||||
.send((our, info.password, networking_keypair, jwt_secret.to_vec()))
|
||||
.await
|
||||
.unwrap();
|
||||
// sender
|
||||
// .send((our, info.password, networking_keypair, jwt_secret.to_vec(), keyfile))
|
||||
// .await
|
||||
// .unwrap();
|
||||
|
||||
let mut response = warp::reply::html("Success".to_string()).into_response();
|
||||
|
||||
|
14
src/types.rs
14
src/types.rs
@ -53,6 +53,20 @@ pub struct Registration {
|
||||
pub direct: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct KeyfileCheck {
|
||||
pub password: String,
|
||||
pub keyfile: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct BootInfo {
|
||||
pub password: String,
|
||||
pub keyfile: String,
|
||||
pub username: String,
|
||||
pub direct: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct Identity {
|
||||
pub name: NodeId,
|
||||
|
Loading…
Reference in New Issue
Block a user