Tweak Patreon authentication implementation

This commit is contained in:
James 2019-11-16 05:43:11 +00:00
parent de4246536e
commit 7392e1ef91
11 changed files with 261 additions and 181 deletions

View File

@ -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");
} }

View File

@ -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(),
) )
} }

View File

@ -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 {
} }
})) }))
} }

View File

@ -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;

View File

@ -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");

View File

@ -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

View File

@ -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);
} }
} }

View File

@ -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;

View File

@ -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()
);
} }
}; };

View File

@ -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> {

View File

@ -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 {