mirror of
https://github.com/yuzu-emu/liftinstall.git
synced 2025-01-03 16:55:48 +00:00
commit
9cf5e745d4
86
Cargo.lock
generated
86
Cargo.lock
generated
|
@ -585,6 +585,20 @@ name = "itoa"
|
|||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "jsonwebtoken"
|
||||
version = "6.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"chrono 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ring 0.14.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.93 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_derive 1.0.93 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"untrusted 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kernel32-sys"
|
||||
version = "0.2.2"
|
||||
|
@ -623,6 +637,7 @@ dependencies = [
|
|||
name = "liftinstall"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cc 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"chrono 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -630,11 +645,12 @@ dependencies = [
|
|||
"fern 0.5.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"hyper 0.11.27 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"jsonwebtoken 6.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"mime_guess 1.8.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"number_prefix 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex 1.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"reqwest 0.9.18 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"reqwest 0.9.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.93 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_derive 1.0.93 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -646,6 +662,7 @@ dependencies = [
|
|||
"url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"walkdir 2.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"web-view 0.4.1 (git+https://github.com/j-selby/web-view.git?rev=752106e4637356cbdb39a0bf1113ea3ae8a14243)",
|
||||
"webbrowser 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"which 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"widestring 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -681,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)",
|
||||
|
@ -733,13 +750,11 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "mime_guess"
|
||||
version = "2.0.0-alpha.6"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"mime 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"phf 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"phf_codegen 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unicase 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unicase 2.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1212,7 +1227,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
version = "0.9.18"
|
||||
version = "0.9.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -1227,7 +1242,7 @@ dependencies = [
|
|||
"hyper-tls 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"mime 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"mime_guess 2.0.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"mime_guess 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.93 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -1240,6 +1255,20 @@ dependencies = [
|
|||
"tokio-timer 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"uuid 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winreg 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ring"
|
||||
version = "0.14.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cc 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"spin 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"untrusted 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1402,6 +1431,11 @@ name = "smallvec"
|
|||
version = "0.6.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "spin"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "stable_deref_trait"
|
||||
version = "1.1.1"
|
||||
|
@ -1823,6 +1857,11 @@ name = "unicode-xid"
|
|||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "untrusted"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "1.7.2"
|
||||
|
@ -1906,6 +1945,15 @@ dependencies = [
|
|||
"webview-sys 0.1.2 (git+https://github.com/j-selby/web-view.git?rev=752106e4637356cbdb39a0bf1113ea3ae8a14243)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webbrowser"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"widestring 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webview-sys"
|
||||
version = "0.1.2"
|
||||
|
@ -1967,6 +2015,14 @@ name = "winapi-x86_64-pc-windows-gnu"
|
|||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "winreg"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winres"
|
||||
version = "0.1.11"
|
||||
|
@ -1997,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]]
|
||||
|
@ -2080,6 +2136,7 @@ dependencies = [
|
|||
"checksum indexmap 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7e81a7c05f79578dbc15793d8b619db9ba32b4577003ef3af1a91c416798c58d"
|
||||
"checksum iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dbe6e417e7d0975db6512b90796e8ce223145ac4e33c377e4a42882a0e88bb08"
|
||||
"checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f"
|
||||
"checksum jsonwebtoken 6.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a81d1812d731546d2614737bee92aa071d37e9afa1409bc374da9e5e70e70b22"
|
||||
"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
|
||||
"checksum language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a"
|
||||
"checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14"
|
||||
|
@ -2088,14 +2145,14 @@ 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"
|
||||
"checksum mime 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ba626b8a6de5da682e1caa06bdb42a335aee5a84db8e5046a3e8ab17ba0a3ae0"
|
||||
"checksum mime 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)" = "3e27ca21f40a310bd06d9031785f4801710d566c184a6e15bad4f1d9b65f9425"
|
||||
"checksum mime_guess 1.8.7 (registry+https://github.com/rust-lang/crates.io-index)" = "0d977de9ee851a0b16e932979515c0f3da82403183879811bc97d50bd9cc50f7"
|
||||
"checksum mime_guess 2.0.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)" = "30de2e4613efcba1ec63d8133f344076952090c122992a903359be5a4f99c3ed"
|
||||
"checksum mime_guess 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1a0ed03949aef72dbdf3116a383d7b38b4768e6f960528cd6a6044aa9ed68599"
|
||||
"checksum miniz_oxide 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c468f2369f07d651a5d0bb2c9079f8488a66d5466efe42d0c5c6466edcb7f71e"
|
||||
"checksum miniz_oxide_c_api 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b7fe927a42e3807ef71defb191dc87d4e24479b221e67015fe38ae2b7b447bab"
|
||||
"checksum mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)" = "83f51996a3ed004ef184e16818edc51fadffe8e7ca68be67f9dee67d84d0ff23"
|
||||
|
@ -2147,7 +2204,8 @@ dependencies = [
|
|||
"checksum regex-syntax 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)" = "9d76410686f9e3a17f06128962e0ecc5755870bb890c34820c7af7f1db2e1d48"
|
||||
"checksum relay 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1576e382688d7e9deecea24417e350d3062d97e32e45d70b1cde65994ff1489a"
|
||||
"checksum remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e"
|
||||
"checksum reqwest 0.9.18 (registry+https://github.com/rust-lang/crates.io-index)" = "00eb63f212df0e358b427f0f40aa13aaea010b470be642ad422bcbca2feff2e4"
|
||||
"checksum reqwest 0.9.21 (registry+https://github.com/rust-lang/crates.io-index)" = "02b7e953e14c6f3102b7e8d1f1ee3abf5ecee80b427f5565c9389835cecae95c"
|
||||
"checksum ring 0.14.6 (registry+https://github.com/rust-lang/crates.io-index)" = "426bc186e3e95cac1e4a4be125a4aca7e84c2d616ffc02244eef36e2a60a093c"
|
||||
"checksum rustc-demangle 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "a7f4dccf6f4891ebcc0c39f9b6eb1a83b9bf5d747cb439ec6fba4f3b977038af"
|
||||
"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
|
||||
"checksum ryu 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "b96a9549dc8d48f2c283938303c4b5a77aa29bfbc5b54b084fb1630408899a8f"
|
||||
|
@ -2171,6 +2229,7 @@ dependencies = [
|
|||
"checksum slug 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b3bc762e6a4b6c6fcaade73e77f9ebc6991b676f88bb2358bddb56560f073373"
|
||||
"checksum smallvec 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4c8cbcd6df1e117c2210e13ab5109635ad68a929fcbb8964dc965b76cb5ee013"
|
||||
"checksum smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "ab606a9c5e214920bb66c458cd7be8ef094f813f20fe77a54cc7dbfff220d4b7"
|
||||
"checksum spin 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
|
||||
"checksum stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8"
|
||||
"checksum string 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d0bbfb8937e38e34c3444ff00afb28b0811d9554f15c5ad64d12b0308d1d1995"
|
||||
"checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
|
||||
|
@ -2213,6 +2272,7 @@ dependencies = [
|
|||
"checksum unicode-normalization 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "141339a08b982d942be2ca06ff8b076563cbe223d1befd5450716790d44e2426"
|
||||
"checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526"
|
||||
"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
|
||||
"checksum untrusted 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "55cd1f4b4e96b46aeb8d4855db4a7a9bd96eeeb5c6a1ab54593328761642ce2f"
|
||||
"checksum url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a"
|
||||
"checksum urlencoding 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3df3561629a8bb4c57e5a2e4c43348d9e29c7c29d9b1c4c1f47166deca8f37ed"
|
||||
"checksum utf8-ranges 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "9d50aa7650df78abf942826607c62468ce18d9019673d4a2ebe1865dbb96ffde"
|
||||
|
@ -2224,6 +2284,7 @@ dependencies = [
|
|||
"checksum want 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "a05d9d966753fa4b5c8db73fcab5eed4549cfe0e1e4e66911e5564a0085c35d1"
|
||||
"checksum want 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b6395efa4784b027708f7451087e647ec73cc74f5d9bc2e418404248d679a230"
|
||||
"checksum web-view 0.4.1 (git+https://github.com/j-selby/web-view.git?rev=752106e4637356cbdb39a0bf1113ea3ae8a14243)" = "<none>"
|
||||
"checksum webbrowser 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "97d468a911faaaeb783693b004e1c62e0063e646b0afae5c146cd144e566e66d"
|
||||
"checksum webview-sys 0.1.2 (git+https://github.com/j-selby/web-view.git?rev=752106e4637356cbdb39a0bf1113ea3ae8a14243)" = "<none>"
|
||||
"checksum which 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b57acb10231b9493c8472b20cb57317d0679a49e0bdbee44b3b803a6473af164"
|
||||
"checksum widestring 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "effc0e4ff8085673ea7b9b2e3c73f6bd4d118810c9009ed8f1e16bd96c331db6"
|
||||
|
@ -2233,6 +2294,7 @@ dependencies = [
|
|||
"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
"checksum winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7168bab6e1daee33b4557efd0e95d5ca70a03706d39fa5f3fe7a236f584b03c9"
|
||||
"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
"checksum winreg 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b2986deb581c4fe11b621998a5e53361efe6b48a151178d0cd9eeffa4dc6acc9"
|
||||
"checksum winres 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "ff4fb510bbfe5b8992ff15f77a2e6fe6cf062878f0eda00c0f44963a807ca5dc"
|
||||
"checksum ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e"
|
||||
"checksum xattr 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "244c3741f4240ef46274860397c7c74e50eb23624996930e484c16679633a54c"
|
||||
|
|
|
@ -15,7 +15,7 @@ futures = "0.1.25"
|
|||
mime_guess = "1.8.6"
|
||||
url = "1.7.2"
|
||||
|
||||
reqwest = "0.9.12"
|
||||
reqwest = "0.9.21"
|
||||
number_prefix = "0.3.0"
|
||||
|
||||
serde = "1.0.89"
|
||||
|
@ -38,6 +38,13 @@ chrono = "0.4.6"
|
|||
|
||||
clap = "2.32.0"
|
||||
|
||||
# used to open a link to the users default browser
|
||||
webbrowser = "0.5.2"
|
||||
# used in JWT based package authentication
|
||||
jsonwebtoken = "6"
|
||||
# used to decode the public key for verifying JWT tokens
|
||||
base64 = "0.10.1"
|
||||
|
||||
[build-dependencies]
|
||||
walkdir = "2.2.7"
|
||||
serde = "1.0.89"
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
name = "yuzu"
|
||||
target_url = "https://raw.githubusercontent.com/yuzu-emu/liftinstall/master/config.linux.v1.toml"
|
||||
target_url = "https://raw.githubusercontent.com/yuzu-emu/liftinstall/master/config.linux.v2.toml"
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
name = "yuzu"
|
||||
target_url = "https://raw.githubusercontent.com/yuzu-emu/liftinstall/master/config.windows.v8.toml"
|
||||
target_url = "https://raw.githubusercontent.com/yuzu-emu/liftinstall/master/config.windows.v9.toml"
|
||||
|
|
52
config.linux.v2.toml
Normal file
52
config.linux.v2.toml
Normal file
|
@ -0,0 +1,52 @@
|
|||
installing_message = "Reminder: yuzu is an <b>experimental</b> emulator. Stuff will break!"
|
||||
hide_advanced = true
|
||||
|
||||
[authentication]
|
||||
# Base64 encoded version of the public key for validating the JWT token. Must be in DER format
|
||||
pub_key_base64 = "MIIBCgKCAQEAs5K6s49JVV9LBMzDrkORsoPSYsv1sCXDtxjp4pn8p0uPSvJAsbNNmdIgCjfSULzbHLM28MblnI4zYP8ZgKtkjdg+Ic5WQbS5iBAkf18zMafpOrotTArLsgZSmUfNYt0SOiN17D+sq/Ov/CKXRM9CttKkEbanBTVqkx7sxsHVbkI6tDvkboSaNeVPHzHlfAbvGrUo5cbAFCB/KnRsoxr+g7jLKTxU1w4xb/pIs91h80AXV/yZPXL6ItPM3/0noIRXjmoeYWf2sFQaFALNB2Kef0p6/hoHYUQP04ZSIL3Q+v13z5X2YJIlI4eLg+iD25QYm9V8oP3+Xro4vd47a0/maQIDAQAB"
|
||||
# URL to authenticate against. This must return a JWT token with their permissions and a custom claim patreonInfo with the following structure
|
||||
# "patreonInfo": { "linked": false, "activeSubscription": false }
|
||||
# If successful, the frontend will use this JWT token as a Bearer Authentication when requesting the binaries to download
|
||||
auth_url = "https://api.yuzu-emu.org/jwt/installer/"
|
||||
[authentication.validation]
|
||||
iss = "citra-core"
|
||||
aud = "installer"
|
||||
|
||||
[[packages]]
|
||||
name = "yuzu"
|
||||
description = "Includes frequent updates to yuzu with all the latest reviewed and tested features."
|
||||
default = true
|
||||
[packages.source]
|
||||
name = "github"
|
||||
match = "^yuzu-linux-[0-9]*-[0-9a-f]*.tar.xz$"
|
||||
[packages.source.config]
|
||||
repo = "yuzu-emu/yuzu-mainline"
|
||||
[[packages.shortcuts]]
|
||||
name = "yuzu"
|
||||
relative_path = "yuzu-windows-msvc/yuzu.exe"
|
||||
description = "Launch yuzu"
|
||||
|
||||
[[packages]]
|
||||
name = "yuzu Early Access"
|
||||
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
|
||||
need_link_description = "You are signed in, but you need to link your Patreon account! Click here for more details"
|
||||
# Displayed when the package has an authentication, but the user has not linked their account
|
||||
need_subscription_description = "You are signed in, but you need to link your Patreon account! Click here for more details"
|
||||
# Displayed when the package has an authentication, but the user has not linked their account
|
||||
need_reward_tier_description = "You are signed in, but are not backing an eligible reward tier! Click here for more details"
|
||||
requires_authorization = true
|
||||
# puts a "new" ribbon the package select
|
||||
is_new = true
|
||||
[packages.source]
|
||||
name = "patreon"
|
||||
match = "^yuzu-linux-[0-9]*-[0-9a-f]*.tar.xz$"
|
||||
[packages.source.config]
|
||||
repo = "earlyaccess"
|
||||
[[packages.shortcuts]]
|
||||
name = "yuzu Early Access"
|
||||
relative_path = "yuzu-linux-earlyaccess/yuzu.exe"
|
||||
description = "Launch yuzu Early Access"
|
||||
|
52
config.windows.v9.toml
Normal file
52
config.windows.v9.toml
Normal file
|
@ -0,0 +1,52 @@
|
|||
installing_message = "Reminder: yuzu is an <b>experimental</b> emulator. Stuff will break!"
|
||||
hide_advanced = true
|
||||
|
||||
[authentication]
|
||||
# Base64 encoded version of the public key for validating the JWT token. Must be in DER format
|
||||
pub_key_base64 = "MIIBCgKCAQEAs5K6s49JVV9LBMzDrkORsoPSYsv1sCXDtxjp4pn8p0uPSvJAsbNNmdIgCjfSULzbHLM28MblnI4zYP8ZgKtkjdg+Ic5WQbS5iBAkf18zMafpOrotTArLsgZSmUfNYt0SOiN17D+sq/Ov/CKXRM9CttKkEbanBTVqkx7sxsHVbkI6tDvkboSaNeVPHzHlfAbvGrUo5cbAFCB/KnRsoxr+g7jLKTxU1w4xb/pIs91h80AXV/yZPXL6ItPM3/0noIRXjmoeYWf2sFQaFALNB2Kef0p6/hoHYUQP04ZSIL3Q+v13z5X2YJIlI4eLg+iD25QYm9V8oP3+Xro4vd47a0/maQIDAQAB"
|
||||
# URL to authenticate against. This must return a JWT token with their permissions and a custom claim patreonInfo with the following structure
|
||||
# "patreonInfo": { "linked": false, "activeSubscription": false }
|
||||
# If successful, the frontend will use this JWT token as a Bearer Authentication when requesting the binaries to download
|
||||
auth_url = "https://api.yuzu-emu.org/jwt/installer/"
|
||||
[authentication.validation]
|
||||
iss = "citra-core"
|
||||
aud = "installer"
|
||||
|
||||
[[packages]]
|
||||
name = "yuzu"
|
||||
description = "Includes frequent updates to yuzu with all the latest reviewed and tested features."
|
||||
default = true
|
||||
[packages.source]
|
||||
name = "github"
|
||||
match = "^yuzu-windows-msvc-[0-9]*-[0-9a-f]*.tar.xz$"
|
||||
[packages.source.config]
|
||||
repo = "yuzu-emu/yuzu-mainline"
|
||||
[[packages.shortcuts]]
|
||||
name = "yuzu"
|
||||
relative_path = "yuzu-windows-msvc/yuzu.exe"
|
||||
description = "Launch yuzu"
|
||||
|
||||
[[packages]]
|
||||
name = "yuzu Early Access"
|
||||
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
|
||||
need_link_description = "You are signed in, but you need to link your Patreon account! Click here for more details"
|
||||
# Displayed when the package has an authentication, but the user has not linked their account
|
||||
need_subscription_description = "You are signed in, but you need to link your Patreon account! Click here for more details"
|
||||
# Displayed when the package has an authentication, but the user has not linked their account
|
||||
need_reward_tier_description = "You are signed in, but are not backing an eligible reward tier! Click here for more details"
|
||||
requires_authorization = true
|
||||
# puts a "new" ribbon the package select
|
||||
is_new = true
|
||||
[packages.source]
|
||||
name = "patreon"
|
||||
match = "^yuzu-windows-msvc-[0-9]*-[0-9a-f]*.tar.xz$"
|
||||
[packages.source.config]
|
||||
repo = "earlyaccess"
|
||||
[[packages.shortcuts]]
|
||||
name = "yuzu Early Access"
|
||||
relative_path = "yuzu-windows-msvc-patreon/yuzu.exe"
|
||||
description = "Launch yuzu Early Access"
|
||||
|
|
@ -36,6 +36,34 @@ pub struct PackageDescription {
|
|||
pub source: PackageSource,
|
||||
#[serde(default)]
|
||||
pub shortcuts: Vec<PackageShortcut>,
|
||||
#[serde(default)]
|
||||
pub requires_authorization: Option<bool>,
|
||||
#[serde(default)]
|
||||
pub is_new: Option<bool>,
|
||||
#[serde(default)]
|
||||
pub need_authentication_description: Option<String>,
|
||||
#[serde(default)]
|
||||
pub need_link_description: Option<String>,
|
||||
#[serde(default)]
|
||||
pub need_subscription_description: Option<String>,
|
||||
#[serde(default)]
|
||||
pub need_reward_tier_description: Option<String>,
|
||||
}
|
||||
|
||||
/// Configuration for validating the JWT token
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct JWTValidation {
|
||||
pub iss: Option<String>,
|
||||
// This can technically be a Vec as well, but thats a pain to support atm
|
||||
pub aud: Option<String>,
|
||||
}
|
||||
|
||||
/// The configuration for this release.
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct AuthenticationConfig {
|
||||
pub pub_key_base64: String,
|
||||
pub auth_url: String,
|
||||
pub validation: Option<JWTValidation>,
|
||||
}
|
||||
|
||||
/// Describes the application itself.
|
||||
|
@ -66,6 +94,8 @@ pub struct Config {
|
|||
pub packages: Vec<PackageDescription>,
|
||||
#[serde(default)]
|
||||
pub hide_advanced: bool,
|
||||
#[serde(default)]
|
||||
pub authentication: Option<AuthenticationConfig>,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
|
|
234
src/frontend/rest/services/authentication.rs
Normal file
234
src/frontend/rest/services/authentication.rs
Normal file
|
@ -0,0 +1,234 @@
|
|||
|
||||
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::sync::Arc;
|
||||
use config::JWTValidation;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct Auth {
|
||||
username: String,
|
||||
token: String,
|
||||
jwt_token: Option<JWTClaims>,
|
||||
}
|
||||
|
||||
/// 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| {
|
||||
format!("Error while converting the response to text {:?}", e)
|
||||
})),
|
||||
_ => {
|
||||
Err(format!("Error wrong response code from server {:?}", response.status()))
|
||||
}
|
||||
}
|
||||
})
|
||||
.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 {
|
||||
let framework = service.framework.read().log_expect("InstallerFramework has been dirtied");
|
||||
let credentials = framework.database.credentials.clone();
|
||||
let config = framework.config.clone().unwrap();
|
||||
|
||||
// If authentication isn't configured, just return immediately
|
||||
if config.authentication.is_none() {
|
||||
return default_future(Response::new().with_status(hyper::Ok).with_body("{}"));
|
||||
}
|
||||
|
||||
// Create moveable framework references so that the lambdas can write to them later
|
||||
let write_cred_fw = Arc::clone(&service.framework);
|
||||
|
||||
Box::new(
|
||||
_req.body().concat2().map(move |body| {
|
||||
let req = form_urlencoded::parse(body.as_ref())
|
||||
.into_owned()
|
||||
.collect::<HashMap<String, String>>();
|
||||
|
||||
// Determine which credentials we should use
|
||||
let (username, token) = {
|
||||
let req_username = req.get("username").unwrap();
|
||||
let req_token = req.get("token").unwrap();
|
||||
// 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();
|
||||
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(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| {
|
||||
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()
|
||||
)
|
||||
}
|
29
src/frontend/rest/services/browser.rs
Normal file
29
src/frontend/rest/services/browser.rs
Normal file
|
@ -0,0 +1,29 @@
|
|||
|
||||
|
||||
use frontend::rest::services::{WebService, Request, Response};
|
||||
use frontend::rest::services::Future as InternalFuture;
|
||||
use futures::{Stream, Future};
|
||||
use url::form_urlencoded;
|
||||
use std::collections::HashMap;
|
||||
use hyper::header::ContentType;
|
||||
|
||||
pub fn handle(_service: &WebService, _req: Request) -> InternalFuture {
|
||||
Box::new(
|
||||
_req.body().concat2().map(move |body| {
|
||||
let req = form_urlencoded::parse(body.as_ref())
|
||||
.into_owned()
|
||||
.collect::<HashMap<String, String>>();
|
||||
if webbrowser::open( req.get("url").unwrap()).is_ok() {
|
||||
Response::new()
|
||||
.with_status(hyper::Ok)
|
||||
.with_header(ContentType::json())
|
||||
.with_body("{}")
|
||||
} else {
|
||||
Response::new()
|
||||
.with_status(hyper::BadRequest)
|
||||
.with_header(ContentType::json())
|
||||
.with_body("{}")
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
|
@ -21,6 +21,8 @@ use futures::future::Future as _;
|
|||
use futures::sink::Sink;
|
||||
|
||||
mod attributes;
|
||||
pub mod authentication;
|
||||
mod browser;
|
||||
mod config;
|
||||
mod default_path;
|
||||
mod dark_mode;
|
||||
|
@ -137,7 +139,9 @@ impl Service for WebService {
|
|||
(Method::Get, "/api/exit") => exit::handle(self, req),
|
||||
(Method::Get, "/api/packages") => packages::handle(self, req),
|
||||
(Method::Get, "/api/installation-status") => installation_status::handle(self, req),
|
||||
(Method::Post, "/api/check-auth") => authentication::handle(self, req),
|
||||
(Method::Post, "/api/start-install") => install::handle(self, req),
|
||||
(Method::Post, "/api/open-browser") => browser::handle(self, req),
|
||||
(Method::Post, "/api/uninstall") => uninstall::handle(self, req),
|
||||
(Method::Post, "/api/update-updater") => update_updater::handle(self, req),
|
||||
(Method::Get, _) => static_files::handle(self, req),
|
||||
|
|
|
@ -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, 550);
|
||||
|
||||
info!("Spawning web view instance");
|
||||
|
||||
|
|
14
src/http.rs
14
src/http.rs
|
@ -36,16 +36,22 @@ pub fn build_async_client() -> Result<AsyncClient, String> {
|
|||
}
|
||||
|
||||
/// Streams a file from a HTTP server.
|
||||
pub fn stream_file<F>(url: &str, mut callback: F) -> Result<(), String>
|
||||
pub fn stream_file<F>(url: &str, authorization: Option<String>, mut callback: F) -> Result<(), String>
|
||||
where
|
||||
F: FnMut(Vec<u8>, u64) -> (),
|
||||
{
|
||||
assert_ssl(url)?;
|
||||
|
||||
let mut client = build_client()?
|
||||
.get(url)
|
||||
let mut client = if authorization.is_some() {
|
||||
build_client()?.get(url)
|
||||
.header("Authorization", format!("Bearer {}", authorization.unwrap()))
|
||||
.send()
|
||||
.map_err(|x| format!("Failed to GET resource: {:?}", x))?;
|
||||
.map_err(|x| format!("Failed to GET resource: {:?}", x))?
|
||||
} else {
|
||||
build_client()?.get(url)
|
||||
.send()
|
||||
.map_err(|x| format!("Failed to GET resource: {:?}", x))?
|
||||
};
|
||||
|
||||
let size = match client.headers().get(CONTENT_LENGTH) {
|
||||
Some(ref v) => v
|
||||
|
|
|
@ -50,14 +50,25 @@ pub enum InstallMessage {
|
|||
Status(String, f64),
|
||||
PackageInstalled,
|
||||
Error(String),
|
||||
AuthorizationRequired(String),
|
||||
EOF,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Default, Clone)]
|
||||
pub struct Credentials {
|
||||
#[serde(default)]
|
||||
pub username: String,
|
||||
#[serde(default)]
|
||||
pub token: String,
|
||||
}
|
||||
|
||||
/// Metadata about the current installation itself.
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
pub struct InstallationDatabase {
|
||||
pub packages: Vec<LocalInstallation>,
|
||||
pub shortcuts: Vec<String>,
|
||||
#[serde(default)]
|
||||
pub credentials: Credentials,
|
||||
}
|
||||
|
||||
impl InstallationDatabase {
|
||||
|
@ -66,6 +77,7 @@ impl InstallationDatabase {
|
|||
InstallationDatabase {
|
||||
packages: Vec::new(),
|
||||
shortcuts: Vec::new(),
|
||||
credentials: Credentials{username: String::new(), token: String::new()},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -114,6 +126,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);
|
||||
|
@ -252,7 +269,7 @@ impl InstallerFramework {
|
|||
let mut downloaded = 0;
|
||||
let mut data_storage: Vec<u8> = Vec::new();
|
||||
|
||||
http::stream_file(tool, |data, size| {
|
||||
http::stream_file(tool, None, |data, size| {
|
||||
{
|
||||
data_storage.extend_from_slice(&data);
|
||||
}
|
||||
|
|
|
@ -47,6 +47,10 @@ extern crate slug;
|
|||
#[cfg(not(windows))]
|
||||
extern crate sysinfo;
|
||||
|
||||
extern crate jsonwebtoken as jwt;
|
||||
|
||||
extern crate base64;
|
||||
|
||||
mod archives;
|
||||
mod config;
|
||||
mod frontend;
|
||||
|
|
|
@ -45,7 +45,7 @@ extern "C" int saveShortcut(
|
|||
const wchar_t *args,
|
||||
const wchar_t *workingDir)
|
||||
{
|
||||
char *errStr = NULL;
|
||||
const char *errStr = NULL;
|
||||
HRESULT h;
|
||||
IShellLink *shellLink = NULL;
|
||||
IPersistFile *persistFile = NULL;
|
||||
|
|
|
@ -107,6 +107,7 @@ impl ReleaseSource for GithubReleases {
|
|||
files.push(File {
|
||||
name: string.to_string(),
|
||||
url: url.to_string(),
|
||||
requires_authorization: false,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -6,12 +6,15 @@ pub mod types;
|
|||
|
||||
pub mod github;
|
||||
|
||||
pub mod patreon;
|
||||
|
||||
use self::types::ReleaseSource;
|
||||
|
||||
/// Returns a ReleaseSource by a name, if possible
|
||||
pub fn get_by_name(name: &str) -> Option<Box<dyn ReleaseSource>> {
|
||||
match name {
|
||||
"github" => Some(Box::new(github::GithubReleases::new())),
|
||||
"patreon" => Some(Box::new(patreon::PatreonReleases::new())),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
|
108
src/sources/patreon.rs
Normal file
108
src/sources/patreon.rs
Normal file
|
@ -0,0 +1,108 @@
|
|||
//! github/mod.rs
|
||||
//!
|
||||
//! Contains the Github API implementation of a release source.
|
||||
|
||||
use sources::types::*;
|
||||
use http::build_client;
|
||||
use reqwest::header::USER_AGENT;
|
||||
use reqwest::StatusCode;
|
||||
|
||||
pub struct PatreonReleases {}
|
||||
|
||||
/// The configuration for this release.
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct PatreonConfig {
|
||||
repo: String,
|
||||
}
|
||||
|
||||
impl PatreonReleases {
|
||||
pub fn new() -> Self {
|
||||
PatreonReleases {}
|
||||
}
|
||||
}
|
||||
|
||||
impl ReleaseSource for PatreonReleases {
|
||||
fn get_current_releases(&self, _config: &TomlValue) -> Result<Vec<Release>, String> {
|
||||
let config: PatreonConfig = match _config.clone().try_into() {
|
||||
Ok(v) => v,
|
||||
Err(v) => return Err(format!("Failed to parse release config: {:?}", v)),
|
||||
};
|
||||
|
||||
let mut results: Vec<Release> = Vec::new();
|
||||
|
||||
// Build the HTTP client up
|
||||
let client = build_client()?;
|
||||
let mut response = client
|
||||
.get(&format!(
|
||||
"https://api.yuzu-emu.org/downloads/{}/",
|
||||
config.repo
|
||||
))
|
||||
.header(USER_AGENT, "liftinstall (j-selby)")
|
||||
.send()
|
||||
.map_err(|x| format!("Error while sending HTTP request: {:?}", x))?;
|
||||
|
||||
match response.status() {
|
||||
StatusCode::OK => {}
|
||||
StatusCode::FORBIDDEN => {
|
||||
return Err(
|
||||
"You are not eligible to download this release".to_string(),
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
return Err(format!("Bad status code: {:?}.", response.status()));
|
||||
}
|
||||
}
|
||||
|
||||
let body = response
|
||||
.text()
|
||||
.map_err(|x| format!("Failed to decode HTTP response body: {:?}", x))?;
|
||||
|
||||
let result: serde_json::Value = serde_json::from_str(&body)
|
||||
.map_err(|x| format!("Failed to parse response: {:?}", x))?;
|
||||
|
||||
// Parse JSON from server
|
||||
let mut files = Vec::new();
|
||||
|
||||
let id: u64 = match result["version"].as_u64() {
|
||||
Some(v) => v,
|
||||
None => return Err("JSON payload missing information about ID".to_string()),
|
||||
};
|
||||
|
||||
let downloads = match result["files"].as_array() {
|
||||
Some(v) => v,
|
||||
None => return Err("JSON payload not an array".to_string()),
|
||||
};
|
||||
|
||||
for file in downloads.iter() {
|
||||
let string = match file["name"].as_str() {
|
||||
Some(v) => v,
|
||||
None => {
|
||||
return Err(
|
||||
"JSON payload missing information about release name".to_string()
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
let url = match file["url"].as_str() {
|
||||
Some(v) => v,
|
||||
None => {
|
||||
return Err(
|
||||
"JSON payload missing information about release URL".to_string()
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
files.push(File {
|
||||
name: string.to_string(),
|
||||
url: url.to_string(),
|
||||
requires_authorization: true,
|
||||
});
|
||||
}
|
||||
|
||||
results.push(Release {
|
||||
version: Version::new_number(id),
|
||||
files,
|
||||
});
|
||||
Ok(results)
|
||||
}
|
||||
}
|
|
@ -66,6 +66,7 @@ impl Ord for Version {
|
|||
pub struct File {
|
||||
pub name: String,
|
||||
pub url: String,
|
||||
pub requires_authorization: bool,
|
||||
}
|
||||
|
||||
impl File {}
|
||||
|
|
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, |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,
|
||||
}
|
||||
|
||||
|
|
|
@ -71,6 +71,7 @@ pre {
|
|||
|
||||
.clickable-box {
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.clickable-box label {
|
||||
|
@ -126,4 +127,48 @@ pre {
|
|||
body.has-background-black-ter .subtitle, body.has-background-black-ter .column > div {
|
||||
color: hsl(0, 0%, 96%);
|
||||
}
|
||||
|
||||
.ribbon {
|
||||
position: absolute;
|
||||
right: -5px; top: -5px;
|
||||
z-index: 1;
|
||||
overflow: hidden;
|
||||
width: 75px; height: 75px;
|
||||
text-align: right;
|
||||
}
|
||||
.ribbon span {
|
||||
font-size: 10px;
|
||||
font-weight: bold;
|
||||
color: #FFF;
|
||||
text-transform: uppercase;
|
||||
text-align: center;
|
||||
line-height: 20px;
|
||||
transform: rotate(45deg);
|
||||
-webkit-transform: rotate(45deg);
|
||||
width: 100px;
|
||||
display: block;
|
||||
background: #79A70A;
|
||||
background: linear-gradient(#FF3C28 0%, #FF3C28 100%);
|
||||
box-shadow: 0 3px 10px -5px rgba(0, 0, 0, 1);
|
||||
position: absolute;
|
||||
top: 19px; right: -21px;
|
||||
}
|
||||
.ribbon span::before {
|
||||
content: "";
|
||||
position: absolute; left: 0px; top: 100%;
|
||||
z-index: -1;
|
||||
border-left: 3px solid #FF3C28;
|
||||
border-right: 3px solid transparent;
|
||||
border-bottom: 3px solid transparent;
|
||||
border-top: 3px solid #FF3C28;
|
||||
}
|
||||
.ribbon span::after {
|
||||
content: "";
|
||||
position: absolute; right: 0px; top: 100%;
|
||||
z-index: -1;
|
||||
border-left: 3px solid transparent;
|
||||
border-right: 3px solid #FF3C28;
|
||||
border-bottom: 3px solid transparent;
|
||||
border-top: 3px solid #FF3C28;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -84,6 +84,13 @@ var app = new Vue({
|
|||
attrs: base_attributes,
|
||||
config: {},
|
||||
install_location: '',
|
||||
username: '',
|
||||
token: '',
|
||||
jwt_token: {},
|
||||
is_authenticated: false,
|
||||
is_linked: false,
|
||||
is_subscribed: false,
|
||||
has_reward_tier: false,
|
||||
// If the option to pick an install location should be provided
|
||||
show_install_location: true,
|
||||
metadata: {
|
||||
|
@ -111,6 +118,34 @@ var app = new Vue({
|
|||
}
|
||||
)
|
||||
},
|
||||
check_authentication: function (success, error) {
|
||||
var that = this;
|
||||
var app = this.$root;
|
||||
|
||||
app.ajax('/api/check-auth', function (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
|
||||
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) {
|
||||
success();
|
||||
}
|
||||
}, function (e) {
|
||||
if (error) {
|
||||
error();
|
||||
}
|
||||
}, {
|
||||
"username": app.$data.username,
|
||||
"token": app.$data.token
|
||||
})
|
||||
},
|
||||
|
||||
ajax: ajax,
|
||||
stream_ajax: stream_ajax
|
||||
}
|
||||
|
|
|
@ -7,6 +7,8 @@ import ErrorView from './views/ErrorView.vue'
|
|||
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)
|
||||
|
||||
|
@ -47,6 +49,16 @@ export default new Router({
|
|||
name: 'modify',
|
||||
component: ModifyView
|
||||
},
|
||||
{
|
||||
path: '/authentication',
|
||||
name: 'authentication',
|
||||
component: AuthenticationView
|
||||
},
|
||||
{
|
||||
path: '/reauthenticate',
|
||||
name: 'reauthenticate',
|
||||
component: ReAuthenticationView
|
||||
},
|
||||
{
|
||||
path: '/',
|
||||
redirect: '/config'
|
||||
|
|
153
ui/src/views/AuthenticationView.vue
Normal file
153
ui/src/views/AuthenticationView.vue
Normal file
|
@ -0,0 +1,153 @@
|
|||
<template>
|
||||
<div class="column has-padding">
|
||||
<b-message type="is-info" :active.sync="browser_opened">
|
||||
Page opened! Check your default browser for the page, and follow the instructions there to link your patreon account.
|
||||
When you are done, enter the token below.
|
||||
</b-message>
|
||||
<b-message type="is-info" :active.sync="show_header">
|
||||
The <strong>Early Access</strong> release channel lets you try out the latest experimental features and fixes, before they are merged into yuzu. This channel includes all regular yuzu daily updates, plus these exclusive features.
|
||||
|
||||
To be an Early Access member, you must be a Patreon Early Access Subscriber.
|
||||
</b-message>
|
||||
<div>
|
||||
If you are a subscriber, <a v-on:click="launch_browser('https://profile.yuzu-emu.org/')">click here to link your yuzu-emu.org account</a>
|
||||
<br>
|
||||
If you are not already a subscriber, <a v-on:click="launch_browser('https://www.patreon.com/join/yuzuteam/checkout?rid=2822069')">click here to become one</a>
|
||||
</div>
|
||||
<br>
|
||||
|
||||
<div class="control">
|
||||
<label for="token">Token</label>
|
||||
<input class="input" type="text" v-model="combined_token" placeholder="Token" id="token">
|
||||
</div>
|
||||
|
||||
<br>
|
||||
|
||||
<b-message type="is-danger" :active.sync="invalid_token">
|
||||
Login failed!
|
||||
Double check that your token is correct and try again
|
||||
</b-message>
|
||||
|
||||
<b-message type="is-danger" :active.sync="invalid_login">
|
||||
Login failed!
|
||||
Double check that your token is correct and try again
|
||||
</b-message>
|
||||
|
||||
<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://profile.yuzu-emu.org/')">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://www.patreon.com/join/yuzuteam/checkout?rid=2822069')">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://www.patreon.com/join/yuzuteam/checkout?rid=2822069')">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">
|
||||
<p class="control">
|
||||
<a class="button is-medium" v-on:click="go_back">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="verify_token">Verify Token</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'AuthenticationView',
|
||||
created: function() {
|
||||
// If they are already authenticated when this page is loaded,
|
||||
// then we can asssume they are "clicking here for more details" and should show the appropriate error message
|
||||
if (this.$root.is_authenticated) {
|
||||
this.verification_opened = true;
|
||||
}
|
||||
},
|
||||
data: function() {
|
||||
return {
|
||||
browser_opened: false,
|
||||
verification_opened: false,
|
||||
invalid_token: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
show_header: function() {
|
||||
return !this.browser_opened && !this.verification_opened && !this.invalid_token;
|
||||
},
|
||||
invalid_login: function() {
|
||||
return this.verification_opened && !this.$root.is_authenticated;
|
||||
},
|
||||
unlinked_patreon: function() {
|
||||
return this.verification_opened && this.$root.is_authenticated && !this.$root.is_linked;
|
||||
},
|
||||
no_subscription: function() {
|
||||
return this.verification_opened && this.$root.is_linked && !this.$root.is_subscribed;
|
||||
},
|
||||
tier_not_selected: function() {
|
||||
return this.verification_opened && this.$root.is_linked && this.$root.is_subscribed && !this.$root.has_reward_tier;
|
||||
},
|
||||
combined_token: {
|
||||
// getter
|
||||
get: function () {
|
||||
if (this.$root.$data.username && this.$root.$data.token) {
|
||||
return btoa(this.$root.$data.username + ":" + this.$root.$data.token)
|
||||
}
|
||||
return "";
|
||||
},
|
||||
// setter
|
||||
set: function (newValue) {
|
||||
try {
|
||||
var split = atob(newValue).split(':')
|
||||
this.$root.$data.username = split[0];
|
||||
this.$root.$data.token = split[1];
|
||||
this.invalid_token = false;
|
||||
} catch (e) {
|
||||
this.invalid_token = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
go_back: function () {
|
||||
this.$router.go(-1)
|
||||
},
|
||||
launch_browser: function(url) {
|
||||
const that = this;
|
||||
let app = this.$root;
|
||||
app.ajax('/api/open-browser', function (e) {
|
||||
// only open the browser opened message if there isn't an error message currently
|
||||
if (!that.verification_opened) {
|
||||
that.browser_opened = true;
|
||||
}
|
||||
}, function (e) {}, {
|
||||
"url": url,
|
||||
});
|
||||
},
|
||||
verify_token: function() {
|
||||
this.browser_opened = false;
|
||||
this.$root.check_authentication(this.success, this.error);
|
||||
},
|
||||
success: function() {
|
||||
// if they are eligible, go back to the select package page
|
||||
if (this.$root.has_reward_tier) {
|
||||
this.$router.go(-1);
|
||||
return;
|
||||
}
|
||||
// They aren't currently eligible for the release, so display the error message
|
||||
this.verification_opened = true;
|
||||
},
|
||||
error: function() {
|
||||
this.verification_opened = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -23,7 +23,7 @@
|
|||
<h4 class="subtitle">Thanks for installing {{ $root.$data.attrs.name }}!</h4>
|
||||
|
||||
<p>You can find your installed applications in your start menu.</p>
|
||||
|
||||
<br>
|
||||
<img src="../assets/how-to-open.png" alt="Where yuzu is installed"/>
|
||||
</div>
|
||||
<div v-else>
|
||||
|
|
|
@ -29,29 +29,27 @@ export default {
|
|||
this.$root.ajax('/api/config', function (e) {
|
||||
that.$root.config = e
|
||||
|
||||
that.choose_next_state()
|
||||
// Update the updater if needed
|
||||
if (that.$root.config.new_tool) {
|
||||
this.$router.push('/install/updater')
|
||||
return
|
||||
}
|
||||
|
||||
that.$root.check_authentication(that.choose_next_state, that.choose_next_state)
|
||||
}, function (e) {
|
||||
console.error('Got error while downloading config: ' +
|
||||
e)
|
||||
console.error('Got error while downloading config: ' + e)
|
||||
|
||||
if (that.$root.metadata.is_launcher) {
|
||||
// Just launch the target application
|
||||
that.$root.exit()
|
||||
} else {
|
||||
that.$router.replace({ name: 'showerr',
|
||||
params: { msg: 'Got error while downloading config: ' +
|
||||
e } })
|
||||
params: { msg: 'Got error while downloading config: ' + e } })
|
||||
}
|
||||
})
|
||||
},
|
||||
choose_next_state: function () {
|
||||
var app = this.$root
|
||||
// Update the updater if needed
|
||||
if (app.config.new_tool) {
|
||||
this.$router.push('/install/updater')
|
||||
return
|
||||
}
|
||||
|
||||
if (app.metadata.preexisting_install) {
|
||||
app.install_location = app.metadata.install_path
|
||||
|
||||
|
|
|
@ -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>
|
|
@ -6,7 +6,8 @@
|
|||
<div class="tile is-ancestor">
|
||||
<div class="tile is-parent" v-for="Lpackage in $root.$data.config.packages" :key="Lpackage.name" :index="Lpackage.name">
|
||||
<div class="tile is-child">
|
||||
<div class="box clickable-box" v-on:click.capture.stop="Lpackage.default = !Lpackage.default">
|
||||
<div class="box clickable-box" v-if="!Lpackage.requires_authorization || (Lpackage.requires_authorization && $root.$data.has_reward_tier)" v-on:click.capture.stop="Lpackage.default = !Lpackage.default">
|
||||
<div class="ribbon" v-if="Lpackage.is_new"><span>New!</span></div>
|
||||
<label class="checkbox">
|
||||
<b-checkbox v-model="Lpackage.default">
|
||||
{{ Lpackage.name }}
|
||||
|
@ -17,6 +18,42 @@
|
|||
{{ Lpackage.description }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="box clickable-box" v-else-if="Lpackage.requires_authorization && !$root.$data.is_authenticated" v-on:click="show_authentication">
|
||||
<div class="ribbon" v-if="Lpackage.is_new"><span>New!</span></div>
|
||||
<b-checkbox>
|
||||
{{ Lpackage.name }}
|
||||
</b-checkbox>
|
||||
<p>
|
||||
{{Lpackage.need_authentication_description}}
|
||||
</p>
|
||||
</div>
|
||||
<div class="box clickable-box" v-else-if="Lpackage.requires_authorization && !$root.$data.is_linked" v-on:click="show_authorization">
|
||||
<div class="ribbon" v-if="Lpackage.is_new"><span>New!</span></div>
|
||||
<b-checkbox>
|
||||
{{ Lpackage.name }}
|
||||
</b-checkbox>
|
||||
<p>
|
||||
{{Lpackage.need_link_description}}
|
||||
</p>
|
||||
</div>
|
||||
<div class="box clickable-box" v-else-if="Lpackage.requires_authorization && !$root.$data.is_subscribed" v-on:click="show_authorization">
|
||||
<div class="ribbon" v-if="Lpackage.is_new"><span>New!</span></div>
|
||||
<b-checkbox>
|
||||
{{ Lpackage.name }}
|
||||
</b-checkbox>
|
||||
<p>
|
||||
{{Lpackage.need_subscription_description}}
|
||||
</p>
|
||||
</div>
|
||||
<div class="box clickable-box" v-else v-on:click="show_authorization">
|
||||
<div class="ribbon" v-if="Lpackage.is_new"><span>New!</span></div>
|
||||
<b-checkbox>
|
||||
{{ Lpackage.name }}
|
||||
</b-checkbox>
|
||||
<p>
|
||||
{{Lpackage.need_reward_tier_description}}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -63,6 +100,20 @@
|
|||
<script>
|
||||
export default {
|
||||
name: 'SelectPackages',
|
||||
created: function() {
|
||||
if (this.$root.$data.has_reward_tier) {
|
||||
for (let package_index = 0; package_index < this.$root.config.packages.length; package_index++) {
|
||||
let current_package = this.$root.config.packages[package_index];
|
||||
// If they are authorized, make the packages that require authorization default
|
||||
if (current_package.requires_authorization) {
|
||||
current_package.default = true;
|
||||
} else {
|
||||
// And unselect any other packages
|
||||
current_package.default = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
data: function () {
|
||||
return {
|
||||
advanced: false
|
||||
|
@ -81,7 +132,13 @@ export default {
|
|||
},
|
||||
go_back: function () {
|
||||
this.$router.go(-1)
|
||||
}
|
||||
},
|
||||
show_authentication: function () {
|
||||
this.$router.push('/authentication')
|
||||
},
|
||||
show_authorization: function () {
|
||||
this.$router.push('/authentication')
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
Loading…
Reference in a new issue