Fully implement the login screen (Closes #6)

This commit is contained in:
Thinkofname 2016-03-20 23:43:31 +00:00
parent b418625a48
commit ae2703418b
7 changed files with 268 additions and 38 deletions

View File

@ -16,6 +16,7 @@
use openssl;
use serde_json;
use hyper;
pub mod mojang;
@ -633,6 +634,8 @@ pub enum Error {
Err(String),
Disconnect(format::Component),
IOError(io::Error),
Json(serde_json::Error),
Hyper(hyper::Error),
}
impl convert::From<io::Error> for Error {
@ -641,12 +644,26 @@ impl convert::From<io::Error> for Error {
}
}
impl convert::From<serde_json::Error> for Error {
fn from(e: serde_json::Error) -> Error {
Error::Json(e)
}
}
impl convert::From<hyper::Error> for Error {
fn from(e: hyper::Error) -> Error {
Error::Hyper(e)
}
}
impl ::std::error::Error for Error {
fn description(&self) -> &str {
match *self {
Error::Err(ref val) => &val[..],
Error::Disconnect(_) => "Disconnect",
Error::IOError(ref e) => e.description(),
Error::Json(ref e) => e.description(),
Error::Hyper(ref e) => e.description(),
}
}
}
@ -657,6 +674,8 @@ impl ::std::fmt::Display for Error {
Error::Err(ref val) => write!(f, "protocol error: {}", val),
Error::Disconnect(ref val) => write!(f, "{}", val),
Error::IOError(ref e) => e.fmt(f),
Error::Json(ref e) => e.fmt(f),
Error::Hyper(ref e) => e.fmt(f),
}
}
}

View File

@ -14,8 +14,10 @@
use openssl;
use serde_json;
use serde_json::builder::ObjectBuilder;
use hyper;
#[derive(Clone, Debug)]
pub struct Profile {
pub username: String,
pub id: String,
@ -23,9 +25,85 @@ pub struct Profile {
}
const JOIN_URL: &'static str = "https://sessionserver.mojang.com/session/minecraft/join";
const LOGIN_URL: &'static str = "https://authserver.mojang.com/authenticate";
const REFRESH_URL: &'static str = "https://authserver.mojang.com/refresh";
const VALIDATE_URL: &'static str = "https://authserver.mojang.com/validate";
impl Profile {
pub fn join_server(&self, server_id: &String, shared_key: &Vec<u8>, public_key: &Vec<u8>) {
pub fn login(username: &str, password: &str, token: &str) -> Result<Profile, super::Error> {
let req_msg = ObjectBuilder::new()
.insert("username", username)
.insert("password", password)
.insert("clientToken", token)
.insert_object("agent", |b| b
.insert("name", "Minecraft")
.insert("version", 1)
)
.unwrap();
let req = try!(serde_json::to_string(&req_msg));
let client = hyper::Client::new();
let res = try!(client.post(LOGIN_URL)
.body(&req)
.header(hyper::header::ContentType("application/json".parse().unwrap()))
.send());
let ret: serde_json::Value = try!(serde_json::from_reader(res));
if let Some(error) = ret.find("error").and_then(|v| v.as_string()) {
return Err(super::Error::Err(format!(
"{}: {}",
error,
ret.find("errorMessage").and_then(|v| v.as_string()).unwrap())
));
}
Ok(Profile {
username: ret.lookup("selectedProfile.name").and_then(|v| v.as_string()).unwrap().to_owned(),
id: ret.lookup("selectedProfile.id").and_then(|v| v.as_string()).unwrap().to_owned(),
access_token: ret.find("accessToken").and_then(|v| v.as_string()).unwrap().to_owned(),
})
}
pub fn refresh(self, token: &str) -> Result<Profile, super::Error> {
let req_msg = ObjectBuilder::new()
.insert("accessToken", self.access_token.clone())
.insert("clientToken", token)
.unwrap();
let req = try!(serde_json::to_string(&req_msg));
let client = hyper::Client::new();
let res = try!(client.post(VALIDATE_URL)
.body(&req)
.header(hyper::header::ContentType("application/json".parse().unwrap()))
.send());
if res.status != hyper::status::StatusCode::NoContent {
println!("Rerefeshing");
// Refresh needed
let res = try!(client.post(REFRESH_URL)
.body(&req)
.header(hyper::header::ContentType("application/json".parse().unwrap()))
.send());
let ret: serde_json::Value = try!(serde_json::from_reader(res));
if let Some(error) = ret.find("error").and_then(|v| v.as_string()) {
return Err(super::Error::Err(format!(
"{}: {}",
error,
ret.find("errorMessage").and_then(|v| v.as_string()).unwrap())
));
}
return Ok(Profile {
username: ret.lookup("selectedProfile.name").and_then(|v| v.as_string()).unwrap().to_owned(),
id: ret.lookup("selectedProfile.id").and_then(|v| v.as_string()).unwrap().to_owned(),
access_token: ret.find("accessToken").and_then(|v| v.as_string()).unwrap().to_owned(),
});
}
println!("Reuse");
Ok(self)
}
// TODO
pub fn join_server(&self, server_id: &str, shared_key: &Vec<u8>, public_key: &Vec<u8>) {
let mut sha1 = openssl::SHA1::new();
sha1.update(server_id.as_bytes());
sha1.update(&shared_key[..]);
@ -46,7 +124,7 @@ impl Profile {
hash_val.to_owned()
};
let join_msg = serde_json::builder::ObjectBuilder::new()
let join_msg = ObjectBuilder::new()
.insert("accessToken", &self.access_token)
.insert("selectedProfile", &self.id)
.insert("serverId", hash_str)
@ -66,6 +144,10 @@ impl Profile {
};
panic!("{:?}", ret);
}
pub fn is_complete(&self) -> bool {
!self.username.is_empty() && !self.id.is_empty() && !self.access_token.is_empty()
}
}
fn twos_compliment(data: &mut Vec<u8>) {

View File

@ -93,9 +93,10 @@ impl super::Screen for Connecting {
fn tick(&mut self,
_delta: f64,
renderer: &mut render::Renderer,
ui_container: &mut ui::Container) {
ui_container: &mut ui::Container) -> Option<Box<super::Screen>>{
let elements = self.elements.as_mut().unwrap();
elements.logo.tick(renderer, ui_container);
None
}
}

View File

@ -14,9 +14,19 @@
use std::sync::{Arc, Mutex};
use std::cell::Cell;
use std::rc::Rc;
use std::sync::mpsc;
use std::thread;
use rand::{self, Rng};
use ui;
use render;
use console;
use protocol;
use protocol::mojang;
use auth;
pub struct Login {
elements: Option<UIElements>,
@ -26,6 +36,17 @@ pub struct Login {
struct UIElements {
logo: ui::logo::Logo,
elements: ui::Collection,
login_btn: ui::ElementRef<ui::Button>,
login_btn_text: ui::ElementRef<ui::Text>,
login_error: ui::ElementRef<ui::Text>,
username_txt: ui::ElementRef<ui::TextBox>,
password_txt: ui::ElementRef<ui::TextBox>,
try_login: Rc<Cell<bool>>,
refresh: bool,
login_res: Option<mpsc::Receiver<Result<mojang::Profile, protocol::Error>>>,
profile: mojang::Profile,
}
@ -40,6 +61,8 @@ impl super::Screen for Login {
let logo = ui::logo::Logo::new(renderer.resources.clone(), renderer, ui_container);
let mut elements = ui::Collection::new();
let try_login = Rc::new(Cell::new(false));
// Login
let (mut login, mut txt) = super::new_button_text(renderer, "Login", 0.0, 100.0, 400.0, 40.0);
login.set_v_attach(ui::VAttach::Middle);
@ -47,15 +70,21 @@ impl super::Screen for Login {
let re = ui_container.add(login);
txt.set_parent(&re);
let tre = ui_container.add(txt);
let tl = try_login.clone();
super::button_action(ui_container,
re.clone(),
Some(tre.clone()),
|game, _| {
game.screen_sys
.replace_screen(Box::new(super::ServerList::new(None)));
});
elements.add(re);
elements.add(tre);
move |_, _| {
tl.set(true);
});
let login_btn = elements.add(re);
let login_btn_text = elements.add(tre);
// Login Error
let mut login_error = ui::Text::new(renderer, "", 0.0, 150.0, 255, 50, 50);
login_error.set_v_attach(ui::VAttach::Middle);
login_error.set_h_attach(ui::HAttach::Center);
let login_error = elements.add(ui_container.add(login_error));
// Username
let mut username = ui::TextBox::new(renderer, "", 0.0, -20.0, 400.0, 40.0);
@ -67,7 +96,7 @@ impl super::Screen for Login {
let ure = ui_container.add(username);
let mut username_label = ui::Text::new(renderer, "Username/Email:", 0.0, -18.0, 255, 255, 255);
username_label.set_parent(&ure);
elements.add(ure);
let username_txt = elements.add(ure);
elements.add(ui_container.add(username_label));
// Password
@ -75,13 +104,14 @@ impl super::Screen for Login {
password.set_v_attach(ui::VAttach::Middle);
password.set_h_attach(ui::HAttach::Center);
password.set_password(true);
password.add_submit_func(|_, _| {
println!("Submit!");
let tl = try_login.clone();
password.add_submit_func(move |_, _| {
tl.set(true);
});
let pre = ui_container.add(password);
let mut password_label = ui::Text::new(renderer, "Password:", 0.0, -18.0, 255, 255, 255);
password_label.set_parent(&pre);
elements.add(pre);
let password_txt = elements.add(pre);
elements.add(ui_container.add(password_label));
// Disclaimer
@ -96,9 +126,28 @@ impl super::Screen for Login {
warn.set_h_attach(ui::HAttach::Right);
elements.add(ui_container.add(warn));
let console = self.console.lock().unwrap();
let profile = mojang::Profile {
username: console.get(auth::CL_USERNAME).clone(),
id: console.get(auth::CL_UUID).clone(),
access_token: console.get(auth::AUTH_TOKEN).clone(),
};
let refresh = profile.is_complete();
try_login.set(refresh);
self.elements = Some(UIElements {
logo: logo,
elements: elements,
profile: profile,
login_btn: login_btn,
login_btn_text: login_btn_text,
login_error: login_error,
try_login: try_login,
refresh: refresh,
login_res: None,
username_txt: username_txt,
password_txt: password_txt,
});
}
fn on_deactive(&mut self, _renderer: &mut render::Renderer, ui_container: &mut ui::Container) {
@ -114,9 +163,79 @@ impl super::Screen for Login {
fn tick(&mut self,
_delta: f64,
renderer: &mut render::Renderer,
ui_container: &mut ui::Container) {
ui_container: &mut ui::Container) -> Option<Box<super::Screen>> {
let elements = self.elements.as_mut().unwrap();
if elements.try_login.get() && elements.login_res.is_none() {
elements.try_login.set(false);
let (tx, rx) = mpsc::channel();
elements.login_res = Some(rx);
{
let btn = ui_container.get_mut(&elements.login_btn);
btn.set_disabled(true);
}
{
let txt = ui_container.get_mut(&elements.login_btn_text);
txt.set_text(renderer, "Logging in...");
}
let mut console = self.console.lock().unwrap();
let mut client_token = console.get(auth::AUTH_CLIENT_TOKEN).clone();
if client_token.is_empty() {
client_token = rand::thread_rng().gen_ascii_chars().take(20).collect::<String>();
console.set(auth::AUTH_CLIENT_TOKEN, client_token);
}
let client_token = console.get(auth::AUTH_CLIENT_TOKEN).clone();
let username = {
let txt = ui_container.get(&elements.username_txt);
txt.get_input()
};
let password = {
let txt = ui_container.get(&elements.password_txt);
txt.get_input()
};
let refresh = elements.refresh;
let profile = elements.profile.clone();
thread::spawn(move || {
if refresh {
tx.send(profile.refresh(&client_token)).unwrap();
} else {
tx.send(mojang::Profile::login(&username, &password, &client_token)).unwrap();
}
});
}
let mut done = false;
if let Some(rx) = elements.login_res.as_ref() {
if let Ok(res) = rx.try_recv() {
done = true;
{
let btn = ui_container.get_mut(&elements.login_btn);
btn.set_disabled(false);
}
{
let txt = ui_container.get_mut(&elements.login_btn_text);
txt.set_text(renderer, "Login");
}
match res {
Ok(val) => {
let mut console = self.console.lock().unwrap();
console.set(auth::CL_USERNAME, val.username.clone());
console.set(auth::CL_UUID, val.id.clone());
console.set(auth::AUTH_TOKEN, val.access_token.clone());
elements.profile = val;
return Some(Box::new(super::ServerList::new(None)));
},
Err(err) => {
let login_error = ui_container.get_mut(&elements.login_error);
login_error.set_text(renderer, &format!("{}", err));
},
}
}
}
if done {
elements.login_res = None;
}
elements.logo.tick(renderer, ui_container);
None
}
}

View File

@ -37,7 +37,7 @@ pub trait Screen {
fn tick(&mut self,
delta: f64,
renderer: &mut render::Renderer,
ui_container: &mut ui::Container);
ui_container: &mut ui::Container) -> Option<Box<Screen>>;
// Events
fn on_scroll(&mut self, x: f64, y: f64) {
@ -106,18 +106,22 @@ impl ScreenSystem {
screen.screen.on_deactive(renderer, ui_container);
}
}
let current = self.screens.last_mut().unwrap();
if !current.init {
current.init = true;
current.screen.init(renderer, ui_container);
}
if !current.active {
current.active = true;
current.screen.on_active(renderer, ui_container);
}
let swap = {
let current = self.screens.last_mut().unwrap();
if !current.init {
current.init = true;
current.screen.init(renderer, ui_container);
}
if !current.active {
current.active = true;
current.screen.on_active(renderer, ui_container);
}
current.screen.tick(delta, renderer, ui_container)
};
// Handle current
current.screen.tick(delta, renderer, ui_container);
if let Some(swap) = swap {
self.replace_screen(swap);
}
}
pub fn on_scroll(&mut self, x: f64, y: f64) {
@ -149,15 +153,19 @@ pub fn button_action<F: Fn(&mut ::Game, &mut ui::Container) + 'static>(ui_contai
click: F) {
let button = ui_container.get_mut(&btn);
button.add_hover_func(move |over, _, ui_container| {
let txt = txt.clone();
if let Some(txt) = txt {
let text = ui_container.get_mut(&txt);
text.set_b(if over {
160
} else {
255
});
}
let disabled = {
let button = ui_container.get_mut(&btn);
button.is_disabled()
};
let txt = txt.clone();
if let Some(txt) = txt {
let text = ui_container.get_mut(&txt);
text.set_b(if over && !disabled {
160
} else {
255
});
}
});
button.add_click_func(click);
}

View File

@ -459,7 +459,7 @@ impl super::Screen for ServerList {
fn tick(&mut self,
delta: f64,
renderer: &mut render::Renderer,
ui_container: &mut ui::Container) {
ui_container: &mut ui::Container) -> Option<Box<super::Screen>> {
if *self.needs_reload.borrow() {
self.reload_server_list(renderer, ui_container);
}
@ -551,6 +551,7 @@ impl super::Screen for ServerList {
}
}
}
None
}
fn on_scroll(&mut self, _: f64, y: f64) {

View File

@ -119,8 +119,8 @@ impl TextBox {
(self.width, self.height)
}
pub fn get_input(&self) -> &str {
&self.input
pub fn get_input(&self) -> String {
self.input.clone()
}
pub fn set_input(&mut self, renderer: &render::Renderer, input: &str) {