Skip to content

Commit

Permalink
Support permissions
Browse files Browse the repository at this point in the history
The actual log-in process is mostly untested
Also add an experimental implementation for <magic-wormhole/magic-wormhole-protocols#6>.
  • Loading branch information
piegamesde committed Jul 31, 2021
1 parent 3a4d638 commit 1086181
Show file tree
Hide file tree
Showing 7 changed files with 279 additions and 82 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ console = { version = "0.14.1", optional = true }
indicatif = { version = "0.16.0", optional = true }
dialoguer = { version = "0.8.0", optional = true }
color-eyre = { version = "0.5.7", optional = true }
hashcash = "0.1.1"

# for some tests
[dev-dependencies]
Expand Down
4 changes: 3 additions & 1 deletion src/bin/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,9 @@ fn enter_code() -> eyre::Result<String> {
}

fn print_welcome(term: &mut Term, welcome: &magic_wormhole::WormholeWelcome) -> eyre::Result<()> {
writeln!(term, "Got welcome from server: {}", &welcome.welcome)?;
if let Some(welcome) = &welcome.welcome {
writeln!(term, "Got welcome from server: {}", welcome)?;
}
Ok(())
}

Expand Down
147 changes: 98 additions & 49 deletions src/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ pub enum WormholeCoreError {
/// The server sent us an error message
#[error("Received error message from server: {}", _0)]
Server(Box<str>),
#[error(
"Server wants one of {:?} for permissions, but we don't suppport any of these",
_0
)]
Login(Vec<String>),
#[error(
"Key confirmation failed. If you didn't mistype the code, \
this is a sign of an attacker guessing passwords. Please try \
Expand Down Expand Up @@ -81,11 +86,16 @@ impl From<std::convert::Infallible> for WormholeCoreError {
// TODO manually implement Debug again to display some Vec<u8> as string and others as hex
#[derive(Debug, derive_more::Display)]
pub enum APIEvent {
#[display(fmt = "ConnectedToServer {{ welcome: {}, code: {} }}", welcome, code)]
#[display(
fmt = "ConnectedToServer {{ motd: {} }}",
r#"motd.as_deref().unwrap_or("<none>")"#
)]
ConnectedToServer {
/// A little welcome message from the server (message of the day and such)
// TODO we can actually provide more structure than a "value", see the protocol
welcome: serde_json::Value,
motd: Option<String>,
},
#[display(fmt = "GotCode {{ code: {} }}", code)]
GotCode {
/// Share this with your peer so they can connect
code: Code,
},
Expand Down Expand Up @@ -126,6 +136,12 @@ pub enum Mood {

#[derive(Debug, derive_more::Display)]
enum State {
#[display(fmt = "")] // TODO
WaitForWelcome {
versions: serde_json::Value,
code_provider: CodeProvider,
},

#[display(
fmt = "AllocatingNameplate {{ wordlist: <{} words>, side: {}, versions: {} }}",
"wordlist.num_words",
Expand Down Expand Up @@ -200,39 +216,12 @@ pub async fn run(

let mut actions: VecDeque<Event> = VecDeque::new();

/* Bootstrapping code */
let mut state;
actions.push_back(OutboundMessage::bind(appid.clone(), side.clone()).into());
/* A mini state machine to track that messaage. It's okay for now, but modularize if it starts growing. */
let mut welcome_message = None;

match code_provider {
CodeProvider::AllocateCode(num_words) => {
// TODO: provide choice of wordlists
let wordlist = Arc::new(wordlist::default_wordlist(num_words));
actions.push_back(OutboundMessage::Allocate.into());

state = State::AllocatingNameplate {
wordlist,
side: side.clone(),
versions,
};
},
CodeProvider::SetCode(code) => {
let code_string = code.to_string();
let nc: Vec<&str> = code_string.splitn(2, '-').collect();
let nameplate = Nameplate::new(nc[0]);
actions.push_back(OutboundMessage::claim(nameplate.clone()).into());

state = State::ClaimingNameplate {
nameplate,
code: Code(code),
side: side.clone(),
versions,
};
},
}
let mut state = State::WaitForWelcome {
versions,
code_provider,
};

/* The usual main loop */
loop {
let e = match actions.pop_front() {
Some(event) => Ok(event),
Expand Down Expand Up @@ -269,7 +258,72 @@ pub async fn run(
use self::{events::Event::*, server_messages::InboundMessage};
match e {
FromIO(InboundMessage::Welcome { welcome }) => {
welcome_message = Some(welcome);
match state {
State::WaitForWelcome {
versions,
code_provider,
} => {
use server_messages::{PermissionRequired, SubmitPermission};

actions
.push_back(APIEvent::ConnectedToServer { motd: welcome.motd }.into());

match welcome.permission_required {
Some(PermissionRequired {
hashcash: Some(hashcash),
..
}) => {
let token = hashcash::Token::new(hashcash.resource, hashcash.bits);
actions.push_back(
OutboundMessage::SubmitPermission(SubmitPermission::Hashcash {
stamp: token.to_string(),
})
.into(),
)
},
Some(PermissionRequired { none: true, .. }) => (),
Some(PermissionRequired { other, .. }) => {
/* We can't actually log in :/ */
actions.push_back(Event::ShutDown(Err(WormholeCoreError::Login(
// TODO use `into_keys` once stable and remove the `cloned`
other.keys().cloned().collect(),
))));
},
None => (),
}

actions
.push_back(OutboundMessage::bind(appid.clone(), side.clone()).into());

match code_provider {
CodeProvider::AllocateCode(num_words) => {
// TODO: provide choice of wordlists
let wordlist = Arc::new(wordlist::default_wordlist(num_words));
actions.push_back(OutboundMessage::Allocate.into());

state = State::AllocatingNameplate {
wordlist,
side: side.clone(),
versions,
};
},
CodeProvider::SetCode(code) => {
let code_string = code.to_string();
let nc: Vec<&str> = code_string.splitn(2, '-').collect();
let nameplate = Nameplate::new(nc[0]);
actions.push_back(OutboundMessage::claim(nameplate.clone()).into());

state = State::ClaimingNameplate {
nameplate,
code: Code(code),
side: side.clone(),
versions,
};
},
}
},
_ => unreachable!(),
}
},
FromIO(InboundMessage::Claimed { mailbox }) => {
match state {
Expand All @@ -291,19 +345,7 @@ pub async fn run(
&code,
)));

actions.push_back(
APIEvent::ConnectedToServer {
/* TODO Is the welcome message mandatory or optional? */
welcome: welcome_message
.take()
.ok_or_else(|| {
anyhow::format_err!("Didn't get a welcome message")
})
.unwrap(),
code,
}
.into(),
);
actions.push_back(APIEvent::GotCode { code }.into());
},
State::Closing { .. } => { /* This may happen. Ignore it. */ },
_ => {
Expand Down Expand Up @@ -420,6 +462,13 @@ pub async fn run(
}
},
ShutDown(result) => match state {
State::WaitForWelcome { .. } => {
state = State::Closing {
await_nameplate_release: false,
await_mailbox_close: false,
result,
};
},
State::AllocatingNameplate { .. } => {
state = State::Closing {
await_nameplate_release: false,
Expand Down
Loading

0 comments on commit 1086181

Please sign in to comment.