Add authentication task dependency to check for auth on install

This commit is contained in:
James Rowe 2019-11-01 11:15:16 -06:00
parent 288518cd78
commit 2b4b59320e
14 changed files with 316 additions and 125 deletions

6
Cargo.lock generated
View File

@ -698,7 +698,7 @@ dependencies = [
[[package]] [[package]]
name = "lzma-sys" name = "lzma-sys"
version = "0.1.14" version = "0.1.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"cc 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)", "cc 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)",
@ -2053,7 +2053,7 @@ name = "xz2"
version = "0.1.6" version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"lzma-sys 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", "lzma-sys 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]
@ -2145,7 +2145,7 @@ dependencies = [
"checksum lock_api 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "62ebf1391f6acad60e5c8b43706dde4582df75c06698ab44511d15016bc2442c" "checksum lock_api 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "62ebf1391f6acad60e5c8b43706dde4582df75c06698ab44511d15016bc2442c"
"checksum log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" "checksum log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b"
"checksum log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c84ec4b527950aa83a329754b01dbe3f58361d1c5efacd1f6d68c494d08a17c6" "checksum log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c84ec4b527950aa83a329754b01dbe3f58361d1c5efacd1f6d68c494d08a17c6"
"checksum lzma-sys 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "16b5c59c57cc4d39e7999f50431aa312ea78af7c93b23fbb0c3567bd672e7f35" "checksum lzma-sys 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "53e48818fd597d46155132bbbb9505d6d1b3d360b4ee25cfa91c406f8a90fe91"
"checksum matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" "checksum matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08"
"checksum memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2efc7bc57c883d4a4d6e3246905283d8dae951bb3bd32f49d6ef297f546e1c39" "checksum memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2efc7bc57c883d4a4d6e3246905283d8dae951bb3bd32f49d6ef297f546e1c39"
"checksum memoffset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0f9dc261e2b62d7a622bf416ea3c5245cdd5d9a7fcc428c0d06804dfce1775b3" "checksum memoffset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0f9dc261e2b62d7a622bf416ea3c5245cdd5d9a7fcc428c0d06804dfce1775b3"

View File

@ -28,7 +28,7 @@ default = true
[[packages]] [[packages]]
name = "yuzu Early Access" name = "yuzu Early Access"
description = "The build for all those epic Chads out there who didn't have to steal 5 dollar from their mom to pay for this." description = "Bonus preview release for project supporters. Thanks for your support!"
# Displayed when the package has no authentication for the user # Displayed when the package has no authentication for the user
need_authentication_description = "Click here to sign in with your yuzu account for Early Access" need_authentication_description = "Click here to sign in with your yuzu account for Early Access"
# Displayed when the package has an authentication, but the user has not linked their account # Displayed when the package has an authentication, but the user has not linked their account

View File

@ -1,5 +1,5 @@
use http::build_async_client; use http::{build_client, build_async_client};
use hyper::header::{ContentLength, ContentType}; use hyper::header::{ContentLength, ContentType};
use reqwest::header::{USER_AGENT}; use reqwest::header::{USER_AGENT};
@ -12,49 +12,130 @@ use logging::LoggingErrors;
use url::form_urlencoded; use url::form_urlencoded;
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
use config::JWTValidation;
/// claims struct, it needs to derive `Serialize` and/or `Deserialize`
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
struct JWTClaims { struct Auth {
sub: String, username: String,
iss: String, token: String,
aud: String, jwt_token: JWTClaims,
exp: usize,
#[serde(default)]
roles: Vec<String>,
#[serde(rename = "releaseChannels", default)]
channels: Vec<String>,
#[serde(rename = "IsPatreonAccountLinked")]
is_linked: bool,
#[serde(rename = "IsPatreonSubscriptionActive")]
is_subscribed: bool,
} }
fn get_text(future: impl Future<Item = reqwest::async::Response, Error = reqwest::Error>) -> impl Future<Item = String, Error = Response> { /// claims struct, it needs to derive `Serialize` and/or `Deserialize`
future.map(|mut response| { #[derive(Debug, Serialize, Deserialize, Clone)]
// Get the body of the response pub struct JWTClaims {
pub sub: String,
pub iss: String,
pub aud: String,
pub exp: usize,
#[serde(default)]
pub roles: Vec<String>,
#[serde(rename = "releaseChannels", default)]
pub channels: Vec<String>,
#[serde(rename = "isPatreonAccountLinked")]
pub is_linked: bool,
#[serde(rename = "isPatreonSubscriptionActive")]
pub is_subscribed: bool,
}
/// 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)
-> Box<dyn futures::Future<Item = String, Error = String>> {
// Build the HTTP client up
let client = match build_async_client() {
Ok(v) => v,
Err(_) => {
return Box::new(futures::future::err("Unable to build async web client".to_string()));
},
};
Box::new(client.post(&url)
.header(USER_AGENT, "liftinstall (j-selby)")
.header("X-USERNAME", username.clone())
.header("X-TOKEN", token.clone())
.send()
.map_err(|err| {
format!("stream error {:?}, client: {:?}, http: {:?}, redirect: {:?}, serialization: {:?}, timeout: {:?}, server: {:?}",
err, err.is_client_error(), err.is_http(), err.is_redirect(),
err.is_serialization(), err.is_timeout(), err.is_server_error())
})
.map(|mut response| {
match response.status() { match response.status() {
reqwest::StatusCode::OK => reqwest::StatusCode::OK =>
Ok(response.text() Ok(response.text()
.map_err(|e| { .map_err(|e| {
error!("Error while converting the response to text {:?}", e); format!("Error while converting the response to text {:?}", e)
Response::new()
.with_status(hyper::StatusCode::InternalServerError)
})), })),
_ => { _ => {
error!("Error wrong response code from server {:?}", response.status()); Err(format!("Error wrong response code from server {:?}", response.status()))
Err(Response::new()
.with_status(hyper::StatusCode::InternalServerError))
} }
} }
}) })
.map_err(|err| {
error!("Error cannot get text on errored stream {:?}", err);
Response::new()
.with_status(hyper::StatusCode::InternalServerError)
})
.and_then(|x| x) .and_then(|x| x)
.flatten() .flatten()
)
}
pub fn authenticate_sync(url: String, username: String, token: String)
-> Result<String, String> {
// Build the HTTP client up
let client = build_client()?;
let mut response = client.post(&url)
.header(USER_AGENT, "liftinstall (j-selby)")
.header("X-USERNAME", username.clone())
.header("X-TOKEN", token.clone())
.send()
.map_err(|err| {
format!("stream error {:?}, client: {:?}, http: {:?}, redirect: {:?}, serialization: {:?}, timeout: {:?}, server: {:?}",
err, err.is_client_error(), err.is_http(), err.is_redirect(),
err.is_serialization(), err.is_timeout(), err.is_server_error())
})?;
match response.status() {
reqwest::StatusCode::OK =>
Ok(response.text()
.map_err(|e| {
format!("Error while converting the response to text {:?}", e)
})?),
_ => {
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> {
// Get the public key for this authentication url
let pub_key = if pub_key_base64.is_empty() {
vec![]
} else {
match base64::decode(&pub_key_base64) {
Ok(v) => v,
Err(err) => {
return Err(format!("Configured public key was not empty and did not decode as base64 {:?}", err));
},
}
};
// Configure validation for audience and issuer if the configuration provides it
let validation = match validation {
Some(v) => {
let mut valid = Validation::new(Algorithm::RS256);
valid.iss = v.iss;
if v.aud.is_some() {
valid.set_audience(&v.aud.unwrap());
}
valid
}
None => Validation::default()
};
// Verify the JWT token
decode::<JWTClaims>(&body, pub_key.as_slice(), &validation)
.map(|tok| tok.claims)
.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 {
@ -93,87 +174,53 @@ pub fn handle(service: &WebService, _req: Request) -> InternalFuture {
(credentials.username.clone(), credentials.token.clone()) (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(); let authentication = config.authentication.unwrap();
let auth_url = authentication.auth_url.clone();
// Get the public key for this authentication url let pub_key_base64 = authentication.pub_key_base64.clone();
let pub_key = if authentication.pub_key_base64.is_empty() { let validation = authentication.validation.clone();
vec![]
} else {
match base64::decode(&authentication.pub_key_base64) {
Ok(v) => v,
Err(err) => {
error!("Configured public key was not empty and did not decode as base64 {:?}", err);
return default_future(Response::new().with_status(hyper::StatusCode::InternalServerError));
},
}
};
// Build the HTTP client up
let client = match build_async_client() {
Ok(v) => v,
Err(_) => {
return default_future(Response::new().with_status(hyper::StatusCode::InternalServerError));
},
};
// call the authentication URL to see if we are authenticated // call the authentication URL to see if we are authenticated
Box::new(get_text( Box::new(authenticate_async(auth_url, username.clone(), token.clone())
client.post(&authentication.auth_url) .map(|body| {
.header(USER_AGENT, "liftinstall (j-selby)") validate_token(body, pub_key_base64, validation)
.header("X-USERNAME", username.clone()) })
.header("X-TOKEN", token.clone()) .and_then(|res| res)
.send() .map(move |claims| {
).map(move |body| { let out = Auth {
// Configure validation for audience and issuer if the configuration provides it username: username_clone,
let validation = match authentication.validation { token: token_clone,
Some(v) => { jwt_token: claims.clone(),
let mut valid = Validation::new(Algorithm::RS256);
valid.iss = v.iss;
if v.aud.is_some() {
valid.set_audience(&v.aud.unwrap());
}
valid
}
None => Validation::default()
}; };
// Verify the JWT token
let tok = match decode::<JWTClaims>(&body, pub_key.as_slice(), &validation) {
Ok(v) => v,
Err(v) => {
error!("Error while decoding the JWT. error: {:?} str: {:?}", v, &body);
return Err(Response::new().with_status(hyper::StatusCode::InternalServerError));
},
};
{
// 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.clone();
framework.database.credentials.token = token.clone();
// And store the JWT token temporarily in the
framework.authorization_token = Some(body.clone());
}
// Convert the json to a string and return the json token // Convert the json to a string and return the json token
match serde_json::to_string(&tok.claims) { match serde_json::to_string(&out) {
Ok(v) => Ok(v), Ok(v) => Ok(v),
Err(e) => { Err(e) => {
error!("Error while converting the claims to JSON string: {:?}", e); Err(format!("Error while converting the claims to JSON string: {:?}", e))
Err(Response::new().with_status(hyper::StatusCode::InternalServerError))
} }
} }
}) })
.and_then(|res| res) .and_then(|res| res)
.map(|out| { .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 // Finally return the JSON with the response
info!("successfully verified username and token"); info!("successfully verified username and token");
Response::new() Response::new()
.with_header(ContentLength(out.len() as u64)) .with_header(ContentLength(json.len() as u64))
.with_header(ContentType::json()) .with_header(ContentType::json())
.with_status(hyper::StatusCode::Ok) .with_status(hyper::StatusCode::Ok)
.with_body(out) .with_body(json)
})
.map_err(|err| {
Response::new().with_status(hyper::StatusCode::InternalServerError)
}) })
.or_else(|err| { .or_else(|err| {
// Convert the Err value into an Ok value since the error code from this HTTP request is an Ok(response) // Convert the Err value into an Ok value since the error code from this HTTP request is an Ok(response)

View File

@ -21,7 +21,7 @@ use futures::future::Future as _;
use futures::sink::Sink; use futures::sink::Sink;
mod attributes; mod attributes;
mod authentication; pub mod authentication;
mod browser; mod browser;
mod config; mod config;
mod default_path; mod default_path;

View File

@ -17,7 +17,7 @@ enum CallbackType {
/// 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 = if is_launcher { (600, 300) } else { (1024, 500) }; let size = (1024, 500);
info!("Spawning web view instance"); info!("Spawning web view instance");

View File

@ -50,6 +50,7 @@ pub enum InstallMessage {
Status(String, f64), Status(String, f64),
PackageInstalled, PackageInstalled,
Error(String), Error(String),
AuthorizationRequired(String),
EOF, EOF,
} }
@ -92,7 +93,6 @@ pub struct InstallerFramework {
// If we just completed an uninstall, and we should clean up after ourselves. // If we just completed an uninstall, and we should clean up after ourselves.
pub burn_after_exit: bool, pub burn_after_exit: bool,
pub launcher_path: Option<String>, pub launcher_path: Option<String>,
pub authorization_token: Option<String>,
} }
/// Contains basic properties on the status of the session. Subset of InstallationFramework. /// Contains basic properties on the status of the session. Subset of InstallationFramework.
@ -125,6 +125,11 @@ macro_rules! declare_messenger_callback {
error!("Failed to submit queue message: {:?}", v); error!("Failed to submit queue message: {:?}", v);
} }
} }
TaskMessage::AuthorizationRequired(msg) => {
if let Err(v) = $target.send(InstallMessage::AuthorizationRequired(msg.to_string())) {
error!("Failed to submit queue message: {:?}", v);
}
}
TaskMessage::PackageInstalled => { TaskMessage::PackageInstalled => {
if let Err(v) = $target.send(InstallMessage::PackageInstalled) { if let Err(v) = $target.send(InstallMessage::PackageInstalled) {
error!("Failed to submit queue message: {:?}", v); error!("Failed to submit queue message: {:?}", v);
@ -441,7 +446,6 @@ impl InstallerFramework {
is_launcher: false, is_launcher: false,
burn_after_exit: false, burn_after_exit: false,
launcher_path: None, launcher_path: None,
authorization_token: None,
} }
} }
@ -469,7 +473,6 @@ impl InstallerFramework {
is_launcher: false, is_launcher: false,
burn_after_exit: false, burn_after_exit: false,
launcher_path: None, launcher_path: None,
authorization_token: None,
}) })
} }
} }

View File

@ -0,0 +1,69 @@
use installer::InstallerFramework;
use tasks::{Task, TaskDependency, TaskMessage, TaskOrdering, TaskParamType};
use logging::LoggingErrors;
use frontend::rest::services::authentication;
use futures::{Stream, Future};
use tasks::resolver::ResolvePackageTask;
pub struct CheckAuthorizationTask {
pub name: String,
}
impl Task for CheckAuthorizationTask {
fn execute(
&mut self,
mut input: Vec<TaskParamType>,
context: &mut InstallerFramework,
messenger: &dyn Fn(&TaskMessage),
) -> Result<TaskParamType, String> {
assert_eq!(input.len(), 1);
let params = input.pop().log_expect("Should have input from resolver!");
let (version, file) = match params {
TaskParamType::File(v, f) => { Ok((v, f)) },
_ => { Err("Unexpected TaskParamType in CheckAuthorization: {:?}") }
}?;
if !file.requires_authorization {
return Ok(TaskParamType::Authentication(version, file, None));
}
let username = context.database.credentials.username.clone();
let token = context.database.credentials.token.clone();
let authentication = context.config.clone().unwrap().authentication.unwrap();
let auth_url = authentication.auth_url.clone();
let pub_key_base64 = authentication.pub_key_base64.clone();
let validation = authentication.validation.clone();
// 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) {
Ok(jwt) => jwt,
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 {
return Ok(TaskParamType::Authentication(version, file, None));
}
Ok(TaskParamType::Authentication(version, file, Some(jwt_token)))
}
fn dependencies(&self) -> Vec<TaskDependency> {
vec![TaskDependency::build(
TaskOrdering::Pre,
Box::new(ResolvePackageTask {
name: self.name.clone(),
}),
)]
}
fn name(&self) -> String {
format!("CheckAuthorizationTask (for {:?})", self.name)
}
}

View File

@ -2,11 +2,8 @@
use installer::InstallerFramework; use installer::InstallerFramework;
use tasks::Task; use tasks::check_authorization::CheckAuthorizationTask;
use tasks::TaskDependency; use tasks::{Task, TaskDependency, TaskMessage, TaskOrdering, TaskParamType};
use tasks::TaskMessage;
use tasks::TaskOrdering;
use tasks::TaskParamType;
use tasks::resolver::ResolvePackageTask; use tasks::resolver::ResolvePackageTask;
@ -30,11 +27,18 @@ impl Task for DownloadPackageTask {
assert_eq!(input.len(), 1); assert_eq!(input.len(), 1);
let file = input.pop().log_expect("Should have input from resolver!"); let file = input.pop().log_expect("Should have input from resolver!");
let (version, file) = match file { let (version, file, auth) = match file {
TaskParamType::File(v, f) => (v, f), TaskParamType::Authentication(v, f, auth) => (v, f, auth),
_ => 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
if file.requires_authorization && auth.is_none() {
info!("Authorization required to update this package!");
messenger(&TaskMessage::AuthorizationRequired("AuthorizationRequired"));
return Ok(TaskParamType::Break);
}
// Check to see if this is the newest file available already // Check to see if this is the newest file available already
for element in &context.database.packages { for element in &context.database.packages {
if element.name == self.name { if element.name == self.name {
@ -54,7 +58,7 @@ impl Task for DownloadPackageTask {
let mut downloaded = 0; let mut downloaded = 0;
let mut data_storage: Vec<u8> = Vec::new(); let mut data_storage: Vec<u8> = Vec::new();
stream_file(&file.url, context.authorization_token.clone(), |data, size| { stream_file(&file.url, auth, |data, size| {
{ {
data_storage.extend_from_slice(&data); data_storage.extend_from_slice(&data);
} }
@ -90,12 +94,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(ResolvePackageTask { Box::new(CheckAuthorizationTask {
name: self.name.clone(), name: self.name.clone(),
}), }),
)] )]
} }
fn name(&self) -> String { fn name(&self) -> String {

View File

@ -9,6 +9,7 @@ use installer::InstallerFramework;
use sources::types::File; use sources::types::File;
use sources::types::Version; use sources::types::Version;
pub mod check_authorization;
pub mod download_pkg; pub mod download_pkg;
pub mod ensure_only_instance; pub mod ensure_only_instance;
pub mod install; pub mod install;
@ -29,6 +30,8 @@ pub enum TaskParamType {
None, None,
/// Metadata about a file /// Metadata about a file
File(Version, File), File(Version, File),
/// Authentication token for a package
Authentication(Version, File, Option<String>),
/// Downloaded contents of a file /// Downloaded contents of a file
FileContents(Version, File, Vec<u8>), FileContents(Version, File, Vec<u8>),
/// List of shortcuts that have been generated /// List of shortcuts that have been generated
@ -62,6 +65,7 @@ impl TaskDependency {
/// A message from a task. /// A message from a task.
pub enum TaskMessage<'a> { pub enum TaskMessage<'a> {
DisplayMessage(&'a str, f64), DisplayMessage(&'a str, f64),
AuthorizationRequired(&'a str),
PackageInstalled, PackageInstalled,
} }

View File

@ -123,7 +123,9 @@ var app = new Vue({
var app = this.$root; var app = this.$root;
app.ajax('/api/check-auth', function (auth) { app.ajax('/api/check-auth', function (auth) {
that.jwt_token = auth; app.$data.username = auth.username;
app.$data.token = auth.token;
that.jwt_token = auth.jwt_token;
that.is_authenticated = Object.keys(that.jwt_token).length !== 0 && that.jwt_token.constructor === Object; that.is_authenticated = Object.keys(that.jwt_token).length !== 0 && that.jwt_token.constructor === Object;
if (that.is_authenticated) { if (that.is_authenticated) {
// Give all permissions to vip roles // Give all permissions to vip roles
@ -132,9 +134,9 @@ var app = new Vue({
that.is_subscribed = true; that.is_subscribed = true;
that.has_reward_tier = true; that.has_reward_tier = true;
} else { } else {
that.is_linked = that.jwt_token.IsPatreonAccountLinked; that.is_linked = that.jwt_token.isPatreonAccountLinked;
that.is_subscribed = that.jwt_token.IsPatreonSubscriptionActive; that.is_subscribed = that.jwt_token.isPatreonSubscriptionActive;
that.has_reward_tier = that.jwt_token.releaseChannels.indexOf("early-release") > -1; that.has_reward_tier = that.jwt_token.releaseChannels.indexOf("early-access") > -1;
} }
} }
if (success) { if (success) {

View File

@ -8,6 +8,7 @@ import InstallPackages from './views/InstallPackages.vue'
import CompleteView from './views/CompleteView.vue' import CompleteView from './views/CompleteView.vue'
import ModifyView from './views/ModifyView.vue' import ModifyView from './views/ModifyView.vue'
import AuthenticationView from './views/AuthenticationView.vue' import AuthenticationView from './views/AuthenticationView.vue'
import ReAuthenticationView from './views/ReAuthenticationView.vue'
Vue.use(Router) Vue.use(Router)
@ -53,6 +54,11 @@ export default new Router({
name: 'authentication', name: 'authentication',
component: AuthenticationView component: AuthenticationView
}, },
{
path: '/reauthenticate',
name: 'reauthenticate',
component: ReAuthenticationView
},
{ {
path: '/', path: '/',
redirect: '/config' redirect: '/config'

View File

@ -6,7 +6,7 @@
</b-message> </b-message>
<p> <p>
Before you can install this Early Access, you need to verify your account. Before you can install this Early Access, you need to verify your account.
<a v-on:click="launch_browser('https://yuzu-emu.org/')">Click here to link your yuzu-emu.org account</a> <a v-on:click="launch_browser('https://profile.yuzu-emu.org/external/patreon/connect/')">Click here to link your yuzu-emu.org account</a>
and paste the token below. and paste the token below.
</p> </p>
@ -30,17 +30,17 @@
<b-message type="is-danger" :active.sync="unlinked_patreon"> <b-message type="is-danger" :active.sync="unlinked_patreon">
Your credentials are valid, but you still need to link your patreon! Your credentials are valid, but you still need to link your patreon!
If this is an error, then <a v-on:click="launch_browser('https://yuzu-emu.org/')">click here to link your yuzu-emu.org account</a> If this is an error, then <a v-on:click="launch_browser('https://profile.yuzu-emu.org/external/patreon/connect/')">click here to link your yuzu-emu.org account</a>
</b-message> </b-message>
<b-message type="is-danger" :active.sync="no_subscription"> <b-message type="is-danger" :active.sync="no_subscription">
Your patreon is linked, but you are not a current subscriber. Your patreon is linked, but you are not a current subscriber.
<a v-on:click="launch_browser('https://patreon.com/')">Log into your patreon account</a> and support the project! <a v-on:click="launch_browser('https://profile.yuzu-emu.org/')">Log into your patreon account</a> and support the project!
</b-message> </b-message>
<b-message type="is-danger" :active.sync="tier_not_selected"> <b-message type="is-danger" :active.sync="tier_not_selected">
Your patreon is linked, and you are supporting the project, but you must first join the Early Access reward tier! Your patreon is linked, and you are supporting the project, but you must first join the Early Access reward tier!
<a v-on:click="launch_browser('https://patreon.com/')">Log into your patreon account</a> and choose to back the Early Access reward tier. <a v-on:click="launch_browser('https://profile.yuzu-emu.org/')">Log into your patreon account</a> and choose to back the Early Access reward tier.
</b-message> </b-message>
<div class="is-left-floating is-bottom-floating"> <div class="is-left-floating is-bottom-floating">

View File

@ -25,6 +25,7 @@ export default {
is_updater_update: false, is_updater_update: false,
is_update: false, is_update: false,
failed_with_error: false, failed_with_error: false,
authorization_required: false,
packages_installed: 0 packages_installed: 0
} }
}, },
@ -41,10 +42,12 @@ export default {
var app = this.$root var app = this.$root
var results = {} var results = {}
var requires_authorization = false;
for (var package_index = 0; package_index < app.config.packages.length; package_index++) { for (var package_index = 0; package_index < app.config.packages.length; package_index++) {
var current_package = app.config.packages[package_index] var current_package = app.config.packages[package_index]
if (current_package.default != null) { if (current_package.default != null) {
requires_authorization |= current_package.requires_authorization;
results[current_package.name] = current_package.default results[current_package.name] = current_package.default
} }
} }
@ -71,6 +74,10 @@ export default {
that.packages_installed += 1 that.packages_installed += 1
} }
if (line.hasOwnProperty('AuthorizationRequired')) {
that.authorization_required = true
}
if (line.hasOwnProperty('Error')) { if (line.hasOwnProperty('Error')) {
that.failed_with_error = true that.failed_with_error = true
that.$router.replace({ name: 'showerr', params: { msg: line.Error } }) that.$router.replace({ name: 'showerr', params: { msg: line.Error } })
@ -90,7 +97,9 @@ export default {
} }
} }
} else { } else {
if (app.metadata.is_launcher) { if (that.authorization_required) {
that.$router.push('/reauthenticate')
} else if (app.metadata.is_launcher) {
app.exit() app.exit()
} else if (!that.failed_with_error) { } else if (!that.failed_with_error) {
if (that.is_uninstall) { if (that.is_uninstall) {

View File

@ -0,0 +1,47 @@
<template>
<div class="column has-padding">
<b-message type="is-info">
<h4 class="subtitle">Update Available!</h4>
There is a new Early Access update available, but you are no longer in the early access reward tier.
We'd love to have you check out this update, so <a v-on:click="go_authenticate">click here to refresh your access!</a>
</b-message>
<br>
Alternatively, you can install the regular version of yuzu by clicking <strong>Back</strong> below.
<br>
Click <strong>Launch Old Version</strong> to continue using the old version of yuzu Early Access.
<div class="is-left-floating is-bottom-floating">
<p class="control">
<a class="button is-medium" v-on:click="go_packages">Back</a>
</p>
</div>
<div class="is-right-floating is-bottom-floating">
<p class="control">
<a class="button is-dark is-medium" v-on:click="launch_old_version">Launch Old Version</a>
</p>
</div>
</div>
</template>
<script>
export default {
name: "ReAuthenticationView",
methods: {
go_authenticate: function() {
this.$router.replace('/authentication')
},
launch_old_version: function () {
this.$root.exit()
},
go_packages: function () {
this.$router.push('/packages')
}
}
}
</script>
<style scoped>
</style>