mirror of
https://github.com/yuzu-emu/liftinstall.git
synced 2024-11-26 00:15:40 +01:00
Tweak Patreon authentication implementation
This commit is contained in:
parent
de4246536e
commit
7392e1ef91
10
build.rs
10
build.rs
@ -87,8 +87,8 @@ fn main() {
|
|||||||
// Copy for the main build
|
// Copy for the main build
|
||||||
copy(&target_config, output_dir.join("bootstrap.toml")).expect("Unable to copy config file");
|
copy(&target_config, output_dir.join("bootstrap.toml")).expect("Unable to copy config file");
|
||||||
|
|
||||||
let yarn_binary = which::which("yarn")
|
let yarn_binary =
|
||||||
.expect("Failed to find yarn - please go ahead and install it!");
|
which::which("yarn").expect("Failed to find yarn - please go ahead and install it!");
|
||||||
|
|
||||||
// Build and deploy frontend files
|
// Build and deploy frontend files
|
||||||
Command::new(&yarn_binary)
|
Command::new(&yarn_binary)
|
||||||
@ -100,7 +100,8 @@ fn main() {
|
|||||||
.arg(ui_dir.to_str().expect("Unable to covert path"))
|
.arg(ui_dir.to_str().expect("Unable to covert path"))
|
||||||
.spawn()
|
.spawn()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.wait().expect("Unable to install Node.JS dependencies using Yarn");
|
.wait()
|
||||||
|
.expect("Unable to install Node.JS dependencies using Yarn");
|
||||||
Command::new(&yarn_binary)
|
Command::new(&yarn_binary)
|
||||||
.args(&[
|
.args(&[
|
||||||
"--cwd",
|
"--cwd",
|
||||||
@ -115,5 +116,6 @@ fn main() {
|
|||||||
])
|
])
|
||||||
.spawn()
|
.spawn()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.wait().expect("Unable to build frontend assets using Webpack");
|
.wait()
|
||||||
|
.expect("Unable to build frontend assets using Webpack");
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,29 @@
|
|||||||
|
//! frontend/rest/services/authentication.rs
|
||||||
|
//!
|
||||||
|
//! Provides mechanisms to authenticate users using JWT.
|
||||||
|
|
||||||
use http::{build_client, build_async_client};
|
|
||||||
|
|
||||||
use hyper::header::{ContentLength, ContentType};
|
|
||||||
use reqwest::header::{USER_AGENT};
|
|
||||||
use futures::{Stream, Future};
|
|
||||||
use jwt::{decode, Validation, Algorithm};
|
|
||||||
|
|
||||||
use frontend::rest::services::{WebService, Request, Response, default_future};
|
|
||||||
use frontend::rest::services::Future as InternalFuture;
|
|
||||||
use logging::LoggingErrors;
|
|
||||||
use url::form_urlencoded;
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use futures::{Future, Stream};
|
||||||
|
|
||||||
|
use hyper::header::{ContentLength, ContentType};
|
||||||
|
|
||||||
|
use jwt::{decode, Algorithm, Validation};
|
||||||
|
|
||||||
|
use reqwest::header::USER_AGENT;
|
||||||
|
|
||||||
|
use url::form_urlencoded;
|
||||||
|
|
||||||
|
use frontend::rest::services::Future as InternalFuture;
|
||||||
|
use frontend::rest::services::{default_future, Request, Response, WebService};
|
||||||
|
|
||||||
|
use http::{build_async_client, build_client};
|
||||||
|
|
||||||
use config::JWTValidation;
|
use config::JWTValidation;
|
||||||
|
|
||||||
|
use logging::LoggingErrors;
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
struct Auth {
|
struct Auth {
|
||||||
username: String,
|
username: String,
|
||||||
@ -39,15 +49,19 @@ pub struct JWTClaims {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Calls the given server to obtain a JWT token and returns a Future<String> with the response
|
/// Calls the given server to obtain a JWT token and returns a Future<String> with the response
|
||||||
pub fn authenticate_async(url: String, username: String, token: String)
|
pub fn authenticate_async(
|
||||||
-> Box<dyn futures::Future<Item = String, Error = String>> {
|
url: String,
|
||||||
|
username: String,
|
||||||
|
token: String,
|
||||||
|
) -> Box<dyn futures::Future<Item = String, Error = String>> {
|
||||||
// Build the HTTP client up
|
// Build the HTTP client up
|
||||||
let client = match build_async_client() {
|
let client = match build_async_client() {
|
||||||
Ok(v) => v,
|
Ok(v) => v,
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
return Box::new(futures::future::err("Unable to build async web client".to_string()));
|
return Box::new(futures::future::err(
|
||||||
},
|
"Unable to build async web client".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Box::new(client.post(&url)
|
Box::new(client.post(&url)
|
||||||
@ -77,9 +91,7 @@ pub fn authenticate_async(url: String, username: String, token: String)
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn authenticate_sync(url: String, username: String, token: String)
|
pub fn authenticate_sync(url: String, username: String, token: String) -> Result<String, String> {
|
||||||
-> Result<String, String> {
|
|
||||||
|
|
||||||
// Build the HTTP client up
|
// Build the HTTP client up
|
||||||
let client = build_client()?;
|
let client = build_client()?;
|
||||||
|
|
||||||
@ -95,18 +107,21 @@ pub fn authenticate_sync(url: String, username: String, token: String)
|
|||||||
})?;
|
})?;
|
||||||
|
|
||||||
match response.status() {
|
match response.status() {
|
||||||
reqwest::StatusCode::OK =>
|
reqwest::StatusCode::OK => Ok(response
|
||||||
Ok(response.text()
|
.text()
|
||||||
.map_err(|e| {
|
.map_err(|e| format!("Error while converting the response to text {:?}", e))?),
|
||||||
format!("Error while converting the response to text {:?}", e)
|
_ => Err(format!(
|
||||||
})?),
|
"Error wrong response code from server {:?}",
|
||||||
_ => {
|
response.status()
|
||||||
Err(format!("Error wrong response code from server {:?}", response.status()))
|
)),
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn validate_token(body: String, pub_key_base64: String, validation: Option<JWTValidation>) -> Result<JWTClaims, String> {
|
pub fn validate_token(
|
||||||
|
body: String,
|
||||||
|
pub_key_base64: String,
|
||||||
|
validation: Option<JWTValidation>,
|
||||||
|
) -> Result<JWTClaims, String> {
|
||||||
// Get the public key for this authentication url
|
// Get the public key for this authentication url
|
||||||
let pub_key = if pub_key_base64.is_empty() {
|
let pub_key = if pub_key_base64.is_empty() {
|
||||||
vec![]
|
vec![]
|
||||||
@ -114,8 +129,11 @@ pub fn validate_token(body: String, pub_key_base64: String, validation: Option<J
|
|||||||
match base64::decode(&pub_key_base64) {
|
match base64::decode(&pub_key_base64) {
|
||||||
Ok(v) => v,
|
Ok(v) => v,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
return Err(format!("Configured public key was not empty and did not decode as base64 {:?}", err));
|
return Err(format!(
|
||||||
},
|
"Configured public key was not empty and did not decode as base64 {:?}",
|
||||||
|
err
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -124,24 +142,35 @@ pub fn validate_token(body: String, pub_key_base64: String, validation: Option<J
|
|||||||
Some(v) => {
|
Some(v) => {
|
||||||
let mut valid = Validation::new(Algorithm::RS256);
|
let mut valid = Validation::new(Algorithm::RS256);
|
||||||
valid.iss = v.iss;
|
valid.iss = v.iss;
|
||||||
if v.aud.is_some() {
|
if let &Some(ref v) = &v.aud {
|
||||||
valid.set_audience(&v.aud.unwrap());
|
valid.set_audience(v);
|
||||||
}
|
}
|
||||||
valid
|
valid
|
||||||
}
|
}
|
||||||
None => Validation::default()
|
None => Validation::default(),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Verify the JWT token
|
// Verify the JWT token
|
||||||
decode::<JWTClaims>(&body, pub_key.as_slice(), &validation)
|
decode::<JWTClaims>(&body, pub_key.as_slice(), &validation)
|
||||||
.map(|tok| tok.claims)
|
.map(|tok| tok.claims)
|
||||||
.map_err(|err| format!("Error while decoding the JWT. error: {:?} jwt: {:?}", err, body))
|
.map_err(|err| {
|
||||||
|
format!(
|
||||||
|
"Error while decoding the JWT. error: {:?} jwt: {:?}",
|
||||||
|
err, body
|
||||||
|
)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle(service: &WebService, _req: Request) -> InternalFuture {
|
pub fn handle(service: &WebService, _req: Request) -> InternalFuture {
|
||||||
let framework = service.framework.read().log_expect("InstallerFramework has been dirtied");
|
let framework = service
|
||||||
|
.framework
|
||||||
|
.read()
|
||||||
|
.log_expect("InstallerFramework has been dirtied");
|
||||||
let credentials = framework.database.credentials.clone();
|
let credentials = framework.database.credentials.clone();
|
||||||
let config = framework.config.clone().unwrap();
|
let config = framework
|
||||||
|
.config
|
||||||
|
.clone()
|
||||||
|
.log_expect("No in-memory configuration found");
|
||||||
|
|
||||||
// If authentication isn't configured, just return immediately
|
// If authentication isn't configured, just return immediately
|
||||||
if config.authentication.is_none() {
|
if config.authentication.is_none() {
|
||||||
@ -152,83 +181,100 @@ pub fn handle(service: &WebService, _req: Request) -> InternalFuture {
|
|||||||
let write_cred_fw = Arc::clone(&service.framework);
|
let write_cred_fw = Arc::clone(&service.framework);
|
||||||
|
|
||||||
Box::new(
|
Box::new(
|
||||||
_req.body().concat2().map(move |body| {
|
_req.body()
|
||||||
let req = form_urlencoded::parse(body.as_ref())
|
.concat2()
|
||||||
.into_owned()
|
.map(move |body| {
|
||||||
.collect::<HashMap<String, String>>();
|
let req = form_urlencoded::parse(body.as_ref())
|
||||||
|
.into_owned()
|
||||||
|
.collect::<HashMap<String, String>>();
|
||||||
|
|
||||||
// Determine which credentials we should use
|
// Determine which credentials we should use
|
||||||
let (username, token) = {
|
let (username, token) = {
|
||||||
let req_username = req.get("username").unwrap();
|
let req_username = req.get("username").log_expect("No username in request");
|
||||||
let req_token = req.get("token").unwrap();
|
let req_token = req.get("token").log_expect("No token in request");
|
||||||
// if the user didn't provide credentials, and theres nothing stored in the database, return an early error
|
|
||||||
let req_cred_valid = !req_username.is_empty() && !req_token.is_empty();
|
|
||||||
let stored_cred_valid = !credentials.username.is_empty() && !credentials.token.is_empty();
|
|
||||||
if !req_cred_valid && !stored_cred_valid {
|
|
||||||
info!("No passed in credential and no stored credentials to validate");
|
|
||||||
return default_future(Response::new().with_status(hyper::BadRequest));
|
|
||||||
}
|
|
||||||
if req_cred_valid {
|
|
||||||
(req.get("username").unwrap().clone(), req.get("token").unwrap().clone())
|
|
||||||
} else {
|
|
||||||
(credentials.username.clone(), credentials.token.clone())
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// second copy of the credentials so we can move them into a different closure
|
|
||||||
let (username_clone, token_clone) = (username.clone(), token.clone());
|
|
||||||
|
|
||||||
let authentication = config.authentication.unwrap();
|
// if the user didn't provide credentials, and theres nothing stored in the
|
||||||
let auth_url = authentication.auth_url.clone();
|
// database, return an early error
|
||||||
let pub_key_base64 = authentication.pub_key_base64.clone();
|
let req_cred_valid = !req_username.is_empty() && !req_token.is_empty();
|
||||||
let validation = authentication.validation.clone();
|
let stored_cred_valid =
|
||||||
|
!credentials.username.is_empty() && !credentials.token.is_empty();
|
||||||
|
|
||||||
// call the authentication URL to see if we are authenticated
|
if !req_cred_valid && !stored_cred_valid {
|
||||||
Box::new(authenticate_async(auth_url, username.clone(), token.clone())
|
info!("No passed in credential and no stored credentials to validate");
|
||||||
.map(|body| {
|
return default_future(Response::new().with_status(hyper::BadRequest));
|
||||||
validate_token(body, pub_key_base64, validation)
|
|
||||||
})
|
|
||||||
.and_then(|res| res)
|
|
||||||
.map(move |claims| {
|
|
||||||
let out = Auth {
|
|
||||||
username: username_clone,
|
|
||||||
token: token_clone,
|
|
||||||
jwt_token: Some(claims.clone()),
|
|
||||||
};
|
|
||||||
// Convert the json to a string and return the json token
|
|
||||||
match serde_json::to_string(&out) {
|
|
||||||
Ok(v) => Ok(v),
|
|
||||||
Err(e) => {
|
|
||||||
Err(format!("Error while converting the claims to JSON string: {:?}", e))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.and_then(|res| res)
|
|
||||||
.map(move |json| {
|
|
||||||
{
|
|
||||||
// Store the validated username and password into the installer database
|
|
||||||
let mut framework = write_cred_fw.write().log_expect("InstallerFramework has been dirtied");
|
|
||||||
framework.database.credentials.username = username;
|
|
||||||
framework.database.credentials.token = token;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finally return the JSON with the response
|
if req_cred_valid {
|
||||||
info!("successfully verified username and token");
|
(req_username.clone(), req_token.clone())
|
||||||
Response::new()
|
} else {
|
||||||
.with_header(ContentLength(json.len() as u64))
|
(credentials.username.clone(), credentials.token.clone())
|
||||||
.with_header(ContentType::json())
|
}
|
||||||
.with_status(hyper::StatusCode::Ok)
|
};
|
||||||
.with_body(json)
|
|
||||||
})
|
// second copy of the credentials so we can move them into a different closure
|
||||||
.map_err(|err| {
|
let (username_clone, token_clone) = (username.clone(), token.clone());
|
||||||
Response::new().with_status(hyper::StatusCode::InternalServerError)
|
|
||||||
})
|
let authentication = config
|
||||||
.or_else(|err| {
|
.authentication
|
||||||
// Convert the Err value into an Ok value since the error code from this HTTP request is an Ok(response)
|
.log_expect("No authentication configuration");
|
||||||
Ok(err)
|
let auth_url = authentication.auth_url.clone();
|
||||||
})
|
let pub_key_base64 = authentication.pub_key_base64.clone();
|
||||||
)
|
let validation = authentication.validation.clone();
|
||||||
})
|
|
||||||
// Flatten the internal future into the output response future
|
// call the authentication URL to see if we are authenticated
|
||||||
.flatten()
|
Box::new(
|
||||||
|
authenticate_async(auth_url, username.clone(), token.clone())
|
||||||
|
.map(|body| validate_token(body, pub_key_base64, validation))
|
||||||
|
.and_then(|res| res)
|
||||||
|
.map(move |claims| {
|
||||||
|
let out = Auth {
|
||||||
|
username: username_clone,
|
||||||
|
token: token_clone,
|
||||||
|
jwt_token: Some(claims.clone()),
|
||||||
|
};
|
||||||
|
// Convert the json to a string and return the json token
|
||||||
|
match serde_json::to_string(&out) {
|
||||||
|
Ok(v) => Ok(v),
|
||||||
|
Err(e) => Err(format!(
|
||||||
|
"Error while converting the claims to JSON string: {:?}",
|
||||||
|
e
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.and_then(|res| res)
|
||||||
|
.map(move |json| {
|
||||||
|
{
|
||||||
|
// Store the validated username and password into the installer database
|
||||||
|
let mut framework = write_cred_fw
|
||||||
|
.write()
|
||||||
|
.log_expect("InstallerFramework has been dirtied");
|
||||||
|
framework.database.credentials.username = username;
|
||||||
|
framework.database.credentials.token = token;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally return the JSON with the response
|
||||||
|
info!("successfully verified username and token");
|
||||||
|
Response::new()
|
||||||
|
.with_header(ContentLength(json.len() as u64))
|
||||||
|
.with_header(ContentType::json())
|
||||||
|
.with_status(hyper::StatusCode::Ok)
|
||||||
|
.with_body(json)
|
||||||
|
})
|
||||||
|
.map_err(|err| {
|
||||||
|
error!(
|
||||||
|
"Got an internal error while processing user token: {:?}",
|
||||||
|
err
|
||||||
|
);
|
||||||
|
Response::new().with_status(hyper::StatusCode::InternalServerError)
|
||||||
|
})
|
||||||
|
.or_else(|err| {
|
||||||
|
// Convert the Err value into an Ok value since the error code from
|
||||||
|
// this HTTP request is an Ok(response)
|
||||||
|
Ok(err)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
// Flatten the internal future into the output response future
|
||||||
|
.flatten(),
|
||||||
)
|
)
|
||||||
}
|
}
|
@ -1,19 +1,21 @@
|
|||||||
|
//! frontend/rest/services/browser.rs
|
||||||
|
//!
|
||||||
|
//! Launches the user's web browser on request from the frontend.
|
||||||
|
|
||||||
|
|
||||||
use frontend::rest::services::{WebService, Request, Response};
|
|
||||||
use frontend::rest::services::Future as InternalFuture;
|
use frontend::rest::services::Future as InternalFuture;
|
||||||
use futures::{Stream, Future};
|
use frontend::rest::services::{Request, Response, WebService};
|
||||||
use url::form_urlencoded;
|
use futures::{Future, Stream};
|
||||||
use std::collections::HashMap;
|
|
||||||
use hyper::header::ContentType;
|
use hyper::header::ContentType;
|
||||||
|
use logging::LoggingErrors;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use url::form_urlencoded;
|
||||||
|
|
||||||
pub fn handle(_service: &WebService, _req: Request) -> InternalFuture {
|
pub fn handle(_service: &WebService, _req: Request) -> InternalFuture {
|
||||||
Box::new(
|
Box::new(_req.body().concat2().map(move |body| {
|
||||||
_req.body().concat2().map(move |body| {
|
|
||||||
let req = form_urlencoded::parse(body.as_ref())
|
let req = form_urlencoded::parse(body.as_ref())
|
||||||
.into_owned()
|
.into_owned()
|
||||||
.collect::<HashMap<String, String>>();
|
.collect::<HashMap<String, String>>();
|
||||||
if webbrowser::open( req.get("url").unwrap()).is_ok() {
|
if webbrowser::open(req.get("url").log_expect("No URL to launch")).is_ok() {
|
||||||
Response::new()
|
Response::new()
|
||||||
.with_status(hyper::Ok)
|
.with_status(hyper::Ok)
|
||||||
.with_header(ContentType::json())
|
.with_header(ContentType::json())
|
||||||
@ -26,4 +28,3 @@ pub fn handle(_service: &WebService, _req: Request) -> InternalFuture {
|
|||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,8 +24,8 @@ mod attributes;
|
|||||||
pub mod authentication;
|
pub mod authentication;
|
||||||
mod browser;
|
mod browser;
|
||||||
mod config;
|
mod config;
|
||||||
mod default_path;
|
|
||||||
mod dark_mode;
|
mod dark_mode;
|
||||||
|
mod default_path;
|
||||||
mod exit;
|
mod exit;
|
||||||
mod install;
|
mod install;
|
||||||
mod installation_status;
|
mod installation_status;
|
||||||
|
@ -12,11 +12,11 @@ use log::Level;
|
|||||||
enum CallbackType {
|
enum CallbackType {
|
||||||
SelectInstallDir { callback_name: String },
|
SelectInstallDir { callback_name: String },
|
||||||
Log { msg: String, kind: String },
|
Log { msg: String, kind: String },
|
||||||
Test {}
|
Test {},
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Starts the main web UI. Will return when UI is closed.
|
/// Starts the main web UI. Will return when UI is closed.
|
||||||
pub fn start_ui(app_name: &str, http_address: &str, is_launcher: bool) {
|
pub fn start_ui(app_name: &str, http_address: &str, _is_launcher: bool) {
|
||||||
let size = (1024, 550);
|
let size = (1024, 550);
|
||||||
|
|
||||||
info!("Spawning web view instance");
|
info!("Spawning web view instance");
|
||||||
|
40
src/http.rs
40
src/http.rs
@ -9,6 +9,7 @@ use std::time::Duration;
|
|||||||
|
|
||||||
use reqwest::async::Client as AsyncClient;
|
use reqwest::async::Client as AsyncClient;
|
||||||
use reqwest::Client;
|
use reqwest::Client;
|
||||||
|
use reqwest::StatusCode;
|
||||||
|
|
||||||
/// Asserts that a URL is valid HTTPS, else returns an error.
|
/// Asserts that a URL is valid HTTPS, else returns an error.
|
||||||
pub fn assert_ssl(url: &str) -> Result<(), String> {
|
pub fn assert_ssl(url: &str) -> Result<(), String> {
|
||||||
@ -36,22 +37,39 @@ pub fn build_async_client() -> Result<AsyncClient, String> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Streams a file from a HTTP server.
|
/// Streams a file from a HTTP server.
|
||||||
pub fn stream_file<F>(url: &str, authorization: Option<String>, mut callback: F) -> Result<(), String>
|
pub fn stream_file<F>(
|
||||||
|
url: &str,
|
||||||
|
authorization: Option<String>,
|
||||||
|
mut callback: F,
|
||||||
|
) -> Result<(), String>
|
||||||
where
|
where
|
||||||
F: FnMut(Vec<u8>, u64) -> (),
|
F: FnMut(Vec<u8>, u64) -> (),
|
||||||
{
|
{
|
||||||
assert_ssl(url)?;
|
assert_ssl(url)?;
|
||||||
|
|
||||||
let mut client = if authorization.is_some() {
|
let mut client = build_client()?.get(url);
|
||||||
build_client()?.get(url)
|
|
||||||
.header("Authorization", format!("Bearer {}", authorization.unwrap()))
|
if let Some(auth) = authorization {
|
||||||
.send()
|
client = client.header("Authorization", format!("Bearer {}", auth));
|
||||||
.map_err(|x| format!("Failed to GET resource: {:?}", x))?
|
}
|
||||||
} else {
|
|
||||||
build_client()?.get(url)
|
let mut client = client
|
||||||
.send()
|
.send()
|
||||||
.map_err(|x| format!("Failed to GET resource: {:?}", x))?
|
.map_err(|x| format!("Failed to GET resource: {:?}", x))?;
|
||||||
};
|
|
||||||
|
match client.status() {
|
||||||
|
StatusCode::OK => {}
|
||||||
|
StatusCode::TOO_MANY_REQUESTS => {
|
||||||
|
return Err(
|
||||||
|
"Your token has exceeded the number of daily allowable IP addresses. \
|
||||||
|
Please wait 24 hours and try again."
|
||||||
|
.to_string(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
x => {
|
||||||
|
return Err(format!("Bad status code: {:?}.", x));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let size = match client.headers().get(CONTENT_LENGTH) {
|
let size = match client.headers().get(CONTENT_LENGTH) {
|
||||||
Some(ref v) => v
|
Some(ref v) => v
|
||||||
|
@ -77,7 +77,10 @@ impl InstallationDatabase {
|
|||||||
InstallationDatabase {
|
InstallationDatabase {
|
||||||
packages: Vec::new(),
|
packages: Vec::new(),
|
||||||
shortcuts: Vec::new(),
|
shortcuts: Vec::new(),
|
||||||
credentials: Credentials{username: String::new(), token: String::new()},
|
credentials: Credentials {
|
||||||
|
username: String::new(),
|
||||||
|
token: String::new(),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -127,7 +130,8 @@ macro_rules! declare_messenger_callback {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
TaskMessage::AuthorizationRequired(msg) => {
|
TaskMessage::AuthorizationRequired(msg) => {
|
||||||
if let Err(v) = $target.send(InstallMessage::AuthorizationRequired(msg.to_string())) {
|
if let Err(v) = $target.send(InstallMessage::AuthorizationRequired(msg.to_string()))
|
||||||
|
{
|
||||||
error!("Failed to submit queue message: {:?}", v);
|
error!("Failed to submit queue message: {:?}", v);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -38,9 +38,9 @@ extern crate chrono;
|
|||||||
|
|
||||||
extern crate clap;
|
extern crate clap;
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
extern crate winapi;
|
|
||||||
#[cfg(windows)]
|
|
||||||
extern crate widestring;
|
extern crate widestring;
|
||||||
|
#[cfg(windows)]
|
||||||
|
extern crate winapi;
|
||||||
|
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
extern crate slug;
|
extern crate slug;
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
//! github/mod.rs
|
//! patreon.rs
|
||||||
//!
|
//!
|
||||||
//! Contains the Github API implementation of a release source.
|
//! Contains the yuzu-emu core API implementation of a release source.
|
||||||
|
|
||||||
use sources::types::*;
|
|
||||||
use http::build_client;
|
use http::build_client;
|
||||||
use reqwest::header::USER_AGENT;
|
use reqwest::header::USER_AGENT;
|
||||||
use reqwest::StatusCode;
|
use reqwest::StatusCode;
|
||||||
|
use sources::types::*;
|
||||||
|
|
||||||
pub struct PatreonReleases {}
|
pub struct PatreonReleases {}
|
||||||
|
|
||||||
@ -44,9 +44,7 @@ impl ReleaseSource for PatreonReleases {
|
|||||||
match response.status() {
|
match response.status() {
|
||||||
StatusCode::OK => {}
|
StatusCode::OK => {}
|
||||||
StatusCode::FORBIDDEN => {
|
StatusCode::FORBIDDEN => {
|
||||||
return Err(
|
return Err("You are not eligible to download this release".to_string());
|
||||||
"You are not eligible to download this release".to_string(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
return Err(format!("Bad status code: {:?}.", response.status()));
|
return Err(format!("Bad status code: {:?}.", response.status()));
|
||||||
@ -77,18 +75,14 @@ impl ReleaseSource for PatreonReleases {
|
|||||||
let string = match file["name"].as_str() {
|
let string = match file["name"].as_str() {
|
||||||
Some(v) => v,
|
Some(v) => v,
|
||||||
None => {
|
None => {
|
||||||
return Err(
|
return Err("JSON payload missing information about release name".to_string());
|
||||||
"JSON payload missing information about release name".to_string()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let url = match file["url"].as_str() {
|
let url = match file["url"].as_str() {
|
||||||
Some(v) => v,
|
Some(v) => v,
|
||||||
None => {
|
None => {
|
||||||
return Err(
|
return Err("JSON payload missing information about release URL".to_string());
|
||||||
"JSON payload missing information about release URL".to_string()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
|
//! Validates that users have correct authorization to download packages.
|
||||||
|
|
||||||
|
use frontend::rest::services::authentication;
|
||||||
|
|
||||||
use installer::InstallerFramework;
|
use installer::InstallerFramework;
|
||||||
use tasks::{Task, TaskDependency, TaskMessage, TaskOrdering, TaskParamType};
|
|
||||||
use logging::LoggingErrors;
|
use logging::LoggingErrors;
|
||||||
use frontend::rest::services::authentication;
|
|
||||||
use futures::{Stream, Future};
|
|
||||||
use tasks::resolver::ResolvePackageTask;
|
use tasks::resolver::ResolvePackageTask;
|
||||||
|
use tasks::{Task, TaskDependency, TaskMessage, TaskOrdering, TaskParamType};
|
||||||
|
|
||||||
pub struct CheckAuthorizationTask {
|
pub struct CheckAuthorizationTask {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
@ -15,14 +18,14 @@ impl Task for CheckAuthorizationTask {
|
|||||||
&mut self,
|
&mut self,
|
||||||
mut input: Vec<TaskParamType>,
|
mut input: Vec<TaskParamType>,
|
||||||
context: &mut InstallerFramework,
|
context: &mut InstallerFramework,
|
||||||
messenger: &dyn Fn(&TaskMessage),
|
_messenger: &dyn Fn(&TaskMessage),
|
||||||
) -> Result<TaskParamType, String> {
|
) -> Result<TaskParamType, String> {
|
||||||
|
|
||||||
assert_eq!(input.len(), 1);
|
assert_eq!(input.len(), 1);
|
||||||
|
|
||||||
let params = input.pop().log_expect("Should have input from resolver!");
|
let params = input.pop().log_expect("Should have input from resolver!");
|
||||||
let (version, file) = match params {
|
let (version, file) = match params {
|
||||||
TaskParamType::File(v, f) => { Ok((v, f)) },
|
TaskParamType::File(v, f) => Ok((v, f)),
|
||||||
_ => { Err("Unexpected TaskParamType in CheckAuthorization: {:?}") }
|
_ => Err("Unexpected TaskParamType in CheckAuthorization: {:?}"),
|
||||||
}?;
|
}?;
|
||||||
|
|
||||||
if !file.requires_authorization {
|
if !file.requires_authorization {
|
||||||
@ -31,27 +34,41 @@ impl Task for CheckAuthorizationTask {
|
|||||||
|
|
||||||
let username = context.database.credentials.username.clone();
|
let username = context.database.credentials.username.clone();
|
||||||
let token = context.database.credentials.token.clone();
|
let token = context.database.credentials.token.clone();
|
||||||
let authentication = context.config.clone().unwrap().authentication.unwrap();
|
let authentication = context
|
||||||
|
.config
|
||||||
|
.clone()
|
||||||
|
.log_expect("In-memory configuration doesn't exist")
|
||||||
|
.authentication
|
||||||
|
.log_expect("No authentication configuration exists while checking authorization");
|
||||||
|
|
||||||
let auth_url = authentication.auth_url.clone();
|
let auth_url = authentication.auth_url.clone();
|
||||||
let pub_key_base64 = authentication.pub_key_base64.clone();
|
let pub_key_base64 = authentication.pub_key_base64.clone();
|
||||||
let validation = authentication.validation.clone();
|
let validation = authentication.validation.clone();
|
||||||
|
|
||||||
// Authorizaion is required for this package so post the username and token and get a jwt_token response
|
// Authorizaion is required for this package so post the username and token and get a jwt_token response
|
||||||
let jwt_token = match authentication::authenticate_sync(auth_url, username, token) {
|
let jwt_token = match authentication::authenticate_sync(auth_url, username, token) {
|
||||||
Ok(jwt) => jwt,
|
Ok(jwt) => jwt,
|
||||||
Err(_) => return Ok(TaskParamType::Authentication(version, file, None))
|
Err(_) => return Ok(TaskParamType::Authentication(version, file, None)),
|
||||||
};
|
};
|
||||||
let claims = match authentication::validate_token(jwt_token.clone(), pub_key_base64, validation) {
|
|
||||||
Ok(c) => c,
|
|
||||||
Err(_) => return Ok(TaskParamType::Authentication(version, file, None))
|
|
||||||
};
|
|
||||||
// Validate that they are authorized
|
|
||||||
let authorized =
|
|
||||||
claims.roles.contains(&"vip".to_string()) || (claims.channels.contains(&"early-access".to_string()));
|
|
||||||
|
|
||||||
if !authorized {
|
let claims =
|
||||||
|
match authentication::validate_token(jwt_token.clone(), pub_key_base64, validation) {
|
||||||
|
Ok(c) => c,
|
||||||
|
Err(_) => return Ok(TaskParamType::Authentication(version, file, None)),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Validate that they are authorized
|
||||||
|
if !claims.roles.contains(&"vip".to_string())
|
||||||
|
&& !claims.channels.contains(&"early-access".to_string())
|
||||||
|
{
|
||||||
return Ok(TaskParamType::Authentication(version, file, None));
|
return Ok(TaskParamType::Authentication(version, file, None));
|
||||||
}
|
}
|
||||||
Ok(TaskParamType::Authentication(version, file, Some(jwt_token)))
|
|
||||||
|
Ok(TaskParamType::Authentication(
|
||||||
|
version,
|
||||||
|
file,
|
||||||
|
Some(jwt_token),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dependencies(&self) -> Vec<TaskDependency> {
|
fn dependencies(&self) -> Vec<TaskDependency> {
|
||||||
|
@ -5,8 +5,6 @@ use installer::InstallerFramework;
|
|||||||
use tasks::check_authorization::CheckAuthorizationTask;
|
use tasks::check_authorization::CheckAuthorizationTask;
|
||||||
use tasks::{Task, TaskDependency, TaskMessage, TaskOrdering, TaskParamType};
|
use tasks::{Task, TaskDependency, TaskMessage, TaskOrdering, TaskParamType};
|
||||||
|
|
||||||
use tasks::resolver::ResolvePackageTask;
|
|
||||||
|
|
||||||
use http::stream_file;
|
use http::stream_file;
|
||||||
|
|
||||||
use number_prefix::{NumberPrefix, Prefixed, Standalone};
|
use number_prefix::{NumberPrefix, Prefixed, Standalone};
|
||||||
@ -32,7 +30,7 @@ impl Task for DownloadPackageTask {
|
|||||||
_ => return Err("Unexpected param type to download package".to_string()),
|
_ => return Err("Unexpected param type to download package".to_string()),
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: move this back below checking for latest version after testing is done
|
// TODO: move this back below checking for latest version after testing is done
|
||||||
if file.requires_authorization && auth.is_none() {
|
if file.requires_authorization && auth.is_none() {
|
||||||
info!("Authorization required to update this package!");
|
info!("Authorization required to update this package!");
|
||||||
messenger(&TaskMessage::AuthorizationRequired("AuthorizationRequired"));
|
messenger(&TaskMessage::AuthorizationRequired("AuthorizationRequired"));
|
||||||
@ -94,12 +92,12 @@ impl Task for DownloadPackageTask {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn dependencies(&self) -> Vec<TaskDependency> {
|
fn dependencies(&self) -> Vec<TaskDependency> {
|
||||||
vec![TaskDependency::build(
|
vec![TaskDependency::build(
|
||||||
TaskOrdering::Pre,
|
TaskOrdering::Pre,
|
||||||
Box::new(CheckAuthorizationTask {
|
Box::new(CheckAuthorizationTask {
|
||||||
name: self.name.clone(),
|
name: self.name.clone(),
|
||||||
}),
|
}),
|
||||||
)]
|
)]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn name(&self) -> String {
|
fn name(&self) -> String {
|
||||||
|
Loading…
Reference in New Issue
Block a user