mirror of
https://github.com/yuzu-emu/liftinstall.git
synced 2024-11-25 15:15:50 +01:00
Add authentication task dependency to check for auth on install
This commit is contained in:
parent
288518cd78
commit
2b4b59320e
6
Cargo.lock
generated
6
Cargo.lock
generated
@ -698,7 +698,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "lzma-sys"
|
||||
version = "0.1.14"
|
||||
version = "0.1.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cc 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@ -2053,7 +2053,7 @@ name = "xz2"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
@ -2145,7 +2145,7 @@ dependencies = [
|
||||
"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.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 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"
|
||||
|
@ -28,7 +28,7 @@ default = true
|
||||
|
||||
[[packages]]
|
||||
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
|
||||
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
|
||||
|
@ -1,5 +1,5 @@
|
||||
|
||||
use http::build_async_client;
|
||||
use http::{build_client, build_async_client};
|
||||
|
||||
use hyper::header::{ContentLength, ContentType};
|
||||
use reqwest::header::{USER_AGENT};
|
||||
@ -12,49 +12,130 @@ use logging::LoggingErrors;
|
||||
use url::form_urlencoded;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use config::JWTValidation;
|
||||
|
||||
/// claims struct, it needs to derive `Serialize` and/or `Deserialize`
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct JWTClaims {
|
||||
sub: String,
|
||||
iss: String,
|
||||
aud: String,
|
||||
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,
|
||||
struct Auth {
|
||||
username: String,
|
||||
token: String,
|
||||
jwt_token: JWTClaims,
|
||||
}
|
||||
|
||||
fn get_text(future: impl Future<Item = reqwest::async::Response, Error = reqwest::Error>) -> impl Future<Item = String, Error = Response> {
|
||||
future.map(|mut response| {
|
||||
// Get the body of the response
|
||||
/// claims struct, it needs to derive `Serialize` and/or `Deserialize`
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
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() {
|
||||
reqwest::StatusCode::OK =>
|
||||
Ok(response.text()
|
||||
.map_err(|e| {
|
||||
error!("Error while converting the response to text {:?}", e);
|
||||
Response::new()
|
||||
.with_status(hyper::StatusCode::InternalServerError)
|
||||
format!("Error while converting the response to text {:?}", e)
|
||||
})),
|
||||
_ => {
|
||||
error!("Error wrong response code from server {:?}", response.status());
|
||||
Err(Response::new()
|
||||
.with_status(hyper::StatusCode::InternalServerError))
|
||||
Err(format!("Error wrong response code from server {:?}", response.status()))
|
||||
}
|
||||
}
|
||||
})
|
||||
.map_err(|err| {
|
||||
error!("Error cannot get text on errored stream {:?}", err);
|
||||
Response::new()
|
||||
.with_status(hyper::StatusCode::InternalServerError)
|
||||
})
|
||||
.and_then(|x| x)
|
||||
.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 {
|
||||
@ -93,87 +174,53 @@ pub fn handle(service: &WebService, _req: Request) -> InternalFuture {
|
||||
(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();
|
||||
|
||||
// Get the public key for this authentication url
|
||||
let pub_key = if authentication.pub_key_base64.is_empty() {
|
||||
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));
|
||||
},
|
||||
};
|
||||
let auth_url = authentication.auth_url.clone();
|
||||
let pub_key_base64 = authentication.pub_key_base64.clone();
|
||||
let validation = authentication.validation.clone();
|
||||
|
||||
// call the authentication URL to see if we are authenticated
|
||||
Box::new(get_text(
|
||||
client.post(&authentication.auth_url)
|
||||
.header(USER_AGENT, "liftinstall (j-selby)")
|
||||
.header("X-USERNAME", username.clone())
|
||||
.header("X-TOKEN", token.clone())
|
||||
.send()
|
||||
).map(move |body| {
|
||||
// Configure validation for audience and issuer if the configuration provides it
|
||||
let validation = match authentication.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()
|
||||
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: claims.clone(),
|
||||
};
|
||||
|
||||
// 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
|
||||
match serde_json::to_string(&tok.claims) {
|
||||
match serde_json::to_string(&out) {
|
||||
Ok(v) => Ok(v),
|
||||
Err(e) => {
|
||||
error!("Error while converting the claims to JSON string: {:?}", e);
|
||||
Err(Response::new().with_status(hyper::StatusCode::InternalServerError))
|
||||
Err(format!("Error while converting the claims to JSON string: {:?}", e))
|
||||
}
|
||||
}
|
||||
})
|
||||
.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
|
||||
info!("successfully verified username and token");
|
||||
Response::new()
|
||||
.with_header(ContentLength(out.len() as u64))
|
||||
.with_header(ContentLength(json.len() as u64))
|
||||
.with_header(ContentType::json())
|
||||
.with_status(hyper::StatusCode::Ok)
|
||||
.with_body(out)
|
||||
.with_body(json)
|
||||
})
|
||||
.map_err(|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)
|
||||
|
@ -21,7 +21,7 @@ use futures::future::Future as _;
|
||||
use futures::sink::Sink;
|
||||
|
||||
mod attributes;
|
||||
mod authentication;
|
||||
pub mod authentication;
|
||||
mod browser;
|
||||
mod config;
|
||||
mod default_path;
|
||||
|
@ -17,7 +17,7 @@ enum CallbackType {
|
||||
|
||||
/// Starts the main web UI. Will return when UI is closed.
|
||||
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");
|
||||
|
||||
|
@ -50,6 +50,7 @@ pub enum InstallMessage {
|
||||
Status(String, f64),
|
||||
PackageInstalled,
|
||||
Error(String),
|
||||
AuthorizationRequired(String),
|
||||
EOF,
|
||||
}
|
||||
|
||||
@ -92,7 +93,6 @@ pub struct InstallerFramework {
|
||||
// If we just completed an uninstall, and we should clean up after ourselves.
|
||||
pub burn_after_exit: bool,
|
||||
pub launcher_path: Option<String>,
|
||||
pub authorization_token: Option<String>,
|
||||
}
|
||||
|
||||
/// 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);
|
||||
}
|
||||
}
|
||||
TaskMessage::AuthorizationRequired(msg) => {
|
||||
if let Err(v) = $target.send(InstallMessage::AuthorizationRequired(msg.to_string())) {
|
||||
error!("Failed to submit queue message: {:?}", v);
|
||||
}
|
||||
}
|
||||
TaskMessage::PackageInstalled => {
|
||||
if let Err(v) = $target.send(InstallMessage::PackageInstalled) {
|
||||
error!("Failed to submit queue message: {:?}", v);
|
||||
@ -441,7 +446,6 @@ impl InstallerFramework {
|
||||
is_launcher: false,
|
||||
burn_after_exit: false,
|
||||
launcher_path: None,
|
||||
authorization_token: None,
|
||||
}
|
||||
}
|
||||
|
||||
@ -469,7 +473,6 @@ impl InstallerFramework {
|
||||
is_launcher: false,
|
||||
burn_after_exit: false,
|
||||
launcher_path: None,
|
||||
authorization_token: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
69
src/tasks/check_authorization.rs
Normal file
69
src/tasks/check_authorization.rs
Normal 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)
|
||||
}
|
||||
}
|
@ -2,11 +2,8 @@
|
||||
|
||||
use installer::InstallerFramework;
|
||||
|
||||
use tasks::Task;
|
||||
use tasks::TaskDependency;
|
||||
use tasks::TaskMessage;
|
||||
use tasks::TaskOrdering;
|
||||
use tasks::TaskParamType;
|
||||
use tasks::check_authorization::CheckAuthorizationTask;
|
||||
use tasks::{Task, TaskDependency, TaskMessage, TaskOrdering, TaskParamType};
|
||||
|
||||
use tasks::resolver::ResolvePackageTask;
|
||||
|
||||
@ -30,11 +27,18 @@ impl Task for DownloadPackageTask {
|
||||
assert_eq!(input.len(), 1);
|
||||
|
||||
let file = input.pop().log_expect("Should have input from resolver!");
|
||||
let (version, file) = match file {
|
||||
TaskParamType::File(v, f) => (v, f),
|
||||
let (version, file, auth) = match file {
|
||||
TaskParamType::Authentication(v, f, auth) => (v, f, auth),
|
||||
_ => 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
|
||||
for element in &context.database.packages {
|
||||
if element.name == self.name {
|
||||
@ -54,7 +58,7 @@ impl Task for DownloadPackageTask {
|
||||
let mut downloaded = 0;
|
||||
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);
|
||||
}
|
||||
@ -92,7 +96,7 @@ impl Task for DownloadPackageTask {
|
||||
fn dependencies(&self) -> Vec<TaskDependency> {
|
||||
vec![TaskDependency::build(
|
||||
TaskOrdering::Pre,
|
||||
Box::new(ResolvePackageTask {
|
||||
Box::new(CheckAuthorizationTask {
|
||||
name: self.name.clone(),
|
||||
}),
|
||||
)]
|
||||
|
@ -9,6 +9,7 @@ use installer::InstallerFramework;
|
||||
use sources::types::File;
|
||||
use sources::types::Version;
|
||||
|
||||
pub mod check_authorization;
|
||||
pub mod download_pkg;
|
||||
pub mod ensure_only_instance;
|
||||
pub mod install;
|
||||
@ -29,6 +30,8 @@ pub enum TaskParamType {
|
||||
None,
|
||||
/// Metadata about a file
|
||||
File(Version, File),
|
||||
/// Authentication token for a package
|
||||
Authentication(Version, File, Option<String>),
|
||||
/// Downloaded contents of a file
|
||||
FileContents(Version, File, Vec<u8>),
|
||||
/// List of shortcuts that have been generated
|
||||
@ -62,6 +65,7 @@ impl TaskDependency {
|
||||
/// A message from a task.
|
||||
pub enum TaskMessage<'a> {
|
||||
DisplayMessage(&'a str, f64),
|
||||
AuthorizationRequired(&'a str),
|
||||
PackageInstalled,
|
||||
}
|
||||
|
||||
|
@ -123,7 +123,9 @@ var app = new Vue({
|
||||
var app = this.$root;
|
||||
|
||||
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;
|
||||
if (that.is_authenticated) {
|
||||
// Give all permissions to vip roles
|
||||
@ -132,9 +134,9 @@ var app = new Vue({
|
||||
that.is_subscribed = true;
|
||||
that.has_reward_tier = true;
|
||||
} else {
|
||||
that.is_linked = that.jwt_token.IsPatreonAccountLinked;
|
||||
that.is_subscribed = that.jwt_token.IsPatreonSubscriptionActive;
|
||||
that.has_reward_tier = that.jwt_token.releaseChannels.indexOf("early-release") > -1;
|
||||
that.is_linked = that.jwt_token.isPatreonAccountLinked;
|
||||
that.is_subscribed = that.jwt_token.isPatreonSubscriptionActive;
|
||||
that.has_reward_tier = that.jwt_token.releaseChannels.indexOf("early-access") > -1;
|
||||
}
|
||||
}
|
||||
if (success) {
|
||||
|
@ -8,6 +8,7 @@ import InstallPackages from './views/InstallPackages.vue'
|
||||
import CompleteView from './views/CompleteView.vue'
|
||||
import ModifyView from './views/ModifyView.vue'
|
||||
import AuthenticationView from './views/AuthenticationView.vue'
|
||||
import ReAuthenticationView from './views/ReAuthenticationView.vue'
|
||||
|
||||
Vue.use(Router)
|
||||
|
||||
@ -53,6 +54,11 @@ export default new Router({
|
||||
name: 'authentication',
|
||||
component: AuthenticationView
|
||||
},
|
||||
{
|
||||
path: '/reauthenticate',
|
||||
name: 'reauthenticate',
|
||||
component: ReAuthenticationView
|
||||
},
|
||||
{
|
||||
path: '/',
|
||||
redirect: '/config'
|
||||
|
@ -6,7 +6,7 @@
|
||||
</b-message>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
|
||||
@ -30,17 +30,17 @@
|
||||
|
||||
<b-message type="is-danger" :active.sync="unlinked_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 type="is-danger" :active.sync="no_subscription">
|
||||
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 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!
|
||||
<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>
|
||||
|
||||
<div class="is-left-floating is-bottom-floating">
|
||||
|
@ -25,6 +25,7 @@ export default {
|
||||
is_updater_update: false,
|
||||
is_update: false,
|
||||
failed_with_error: false,
|
||||
authorization_required: false,
|
||||
packages_installed: 0
|
||||
}
|
||||
},
|
||||
@ -41,10 +42,12 @@ export default {
|
||||
var app = this.$root
|
||||
|
||||
var results = {}
|
||||
var requires_authorization = false;
|
||||
|
||||
for (var package_index = 0; package_index < app.config.packages.length; package_index++) {
|
||||
var current_package = app.config.packages[package_index]
|
||||
if (current_package.default != null) {
|
||||
requires_authorization |= current_package.requires_authorization;
|
||||
results[current_package.name] = current_package.default
|
||||
}
|
||||
}
|
||||
@ -71,6 +74,10 @@ export default {
|
||||
that.packages_installed += 1
|
||||
}
|
||||
|
||||
if (line.hasOwnProperty('AuthorizationRequired')) {
|
||||
that.authorization_required = true
|
||||
}
|
||||
|
||||
if (line.hasOwnProperty('Error')) {
|
||||
that.failed_with_error = true
|
||||
that.$router.replace({ name: 'showerr', params: { msg: line.Error } })
|
||||
@ -90,7 +97,9 @@ export default {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (app.metadata.is_launcher) {
|
||||
if (that.authorization_required) {
|
||||
that.$router.push('/reauthenticate')
|
||||
} else if (app.metadata.is_launcher) {
|
||||
app.exit()
|
||||
} else if (!that.failed_with_error) {
|
||||
if (that.is_uninstall) {
|
||||
|
47
ui/src/views/ReAuthenticationView.vue
Normal file
47
ui/src/views/ReAuthenticationView.vue
Normal 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>
|
Loading…
Reference in New Issue
Block a user