From 76447019c0f71e2edd67e066090ba649b3aea06f Mon Sep 17 00:00:00 2001 From: MelanX Date: Sun, 14 Apr 2024 19:10:13 +0200 Subject: [PATCH 1/7] Fix `client-overrides` not getting extracted (#1120) Closes #1112 According to the [docs](https://support.modrinth.com/en/articles/8802351-modrinth-modpack-format-mrpack#h_3ad1b429f0), it needs to be `client-overrides`, not `client_overrides`. This PR fixes this typo. --- theseus/src/api/pack/install_mrpack.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/theseus/src/api/pack/install_mrpack.rs b/theseus/src/api/pack/install_mrpack.rs index d812336f6..42566bf45 100644 --- a/theseus/src/api/pack/install_mrpack.rs +++ b/theseus/src/api/pack/install_mrpack.rs @@ -213,7 +213,7 @@ pub async fn install_zipped_mrpack_files( let filename = file.filename().as_str().unwrap_or_default(); if (filename.starts_with("overrides") - || filename.starts_with("client_overrides")) + || filename.starts_with("client-overrides")) && !filename.ends_with('/') { total_len += 1; @@ -227,7 +227,7 @@ pub async fn install_zipped_mrpack_files( let file_path = PathBuf::from(filename); if (filename.starts_with("overrides") - || filename.starts_with("client_overrides")) + || filename.starts_with("client-overrides")) && !filename.ends_with('/') { // Reads the file into the 'content' variable @@ -403,7 +403,7 @@ pub async fn remove_all_related_files( let file_path = PathBuf::from(filename); if (filename.starts_with("overrides") - || filename.starts_with("client_overrides")) + || filename.starts_with("client-overrides")) && !filename.ends_with('/') { let mut new_path = PathBuf::new(); From 28779196393b59f06bcecbf88c77e49dc297989f Mon Sep 17 00:00:00 2001 From: Geometrically <18202329+Geometrically@users.noreply.github.com> Date: Mon, 15 Apr 2024 13:58:20 -0700 Subject: [PATCH 2/7] Switch to official launcher auth (#1118) * Switch to official launcher auth * add debug info * Fix build --- .github/workflows/cli-build.yml | 44 - .gitignore | 1 + .vscode/extensions.json | 12 - .vscode/launch.json | 160 ---- .vscode/settings.json | 60 -- .vscode/tasks.json | 32 - Cargo.lock | 453 +++++---- Cargo.toml | 1 - theseus.iml | 11 - theseus/Cargo.toml | 7 +- theseus/src/api/auth.rs | 132 --- theseus/src/api/hydra/complete.rs | 84 -- theseus/src/api/hydra/init.rs | 47 - theseus/src/api/hydra/mod.rs | 15 - theseus/src/api/hydra/refresh.rs | 69 -- theseus/src/api/hydra/stages/bearer_token.rs | 42 - theseus/src/api/hydra/stages/mod.rs | 37 - theseus/src/api/hydra/stages/player_info.rs | 46 - theseus/src/api/hydra/stages/poll_response.rs | 99 -- theseus/src/api/hydra/stages/xbl_signin.rs | 59 -- theseus/src/api/hydra/stages/xsts_token.rs | 62 -- theseus/src/api/logs.rs | 20 +- theseus/src/api/minecraft_auth.rs | 76 ++ theseus/src/api/mod.rs | 15 +- theseus/src/api/profile/mod.rs | 29 +- theseus/src/error.rs | 9 +- theseus/src/event/mod.rs | 2 +- theseus/src/launcher/args.rs | 2 +- theseus/src/launcher/auth.rs | 84 -- theseus/src/launcher/mod.rs | 5 +- theseus/src/state/auth_task.rs | 86 -- theseus/src/state/minecraft_auth.rs | 878 ++++++++++++++++++ theseus/src/state/mod.rs | 20 +- theseus/src/state/settings.rs | 28 - theseus/src/state/users.rs | 70 -- theseus_cli/Cargo.toml | 34 - theseus_cli/src/main.rs | 52 -- theseus_cli/src/subcommands/mod.rs | 20 - theseus_cli/src/subcommands/profile.rs | 372 -------- theseus_cli/src/subcommands/user.rs | 181 ---- theseus_cli/src/util.rs | 95 -- theseus_gui/package.json | 1 - theseus_gui/pnpm-lock.yaml | 284 +++--- theseus_gui/src-tauri/src/api/auth.rs | 89 +- theseus_gui/src-tauri/src/api/mod.rs | 6 + theseus_gui/src-tauri/src/main.rs | 1 + theseus_gui/src/App.vue | 26 +- theseus_gui/src/assets/video.mp4 | Bin 3538527 -> 0 bytes .../src/components/ui/AccountsCard.vue | 96 +- theseus_gui/src/components/ui/ErrorModal.vue | 125 +++ .../src/components/ui/RunningAppBar.vue | 2 +- .../ui/tutorial/FakeAccountsCard.vue | 178 ---- .../src/components/ui/tutorial/FakeAppBar.vue | 265 ------ .../ui/tutorial/FakeGridDisplay.vue | 222 ----- .../components/ui/tutorial/FakeRowDisplay.vue | 376 -------- .../src/components/ui/tutorial/FakeSearch.vue | 496 ---------- .../components/ui/tutorial/FakeSettings.vue | 270 ------ .../components/ui/tutorial/ImportingCard.vue | 293 ------ .../src/components/ui/tutorial/LoginCard.vue | 169 +--- .../ui/tutorial/OnboardingScreen.vue | 227 +---- .../ui/tutorial/PreImportScreen.vue | 184 ---- .../components/ui/tutorial/TutorialTip.vue | 72 -- theseus_gui/src/helpers/auth.js | 33 +- theseus_gui/src/store/error.js | 21 + theseus_playground/src/main.rs | 30 +- 65 files changed, 1671 insertions(+), 5346 deletions(-) delete mode 100644 .github/workflows/cli-build.yml delete mode 100644 .vscode/extensions.json delete mode 100644 .vscode/launch.json delete mode 100644 .vscode/settings.json delete mode 100644 .vscode/tasks.json delete mode 100644 theseus.iml delete mode 100644 theseus/src/api/auth.rs delete mode 100644 theseus/src/api/hydra/complete.rs delete mode 100644 theseus/src/api/hydra/init.rs delete mode 100644 theseus/src/api/hydra/mod.rs delete mode 100644 theseus/src/api/hydra/refresh.rs delete mode 100644 theseus/src/api/hydra/stages/bearer_token.rs delete mode 100644 theseus/src/api/hydra/stages/mod.rs delete mode 100644 theseus/src/api/hydra/stages/player_info.rs delete mode 100644 theseus/src/api/hydra/stages/poll_response.rs delete mode 100644 theseus/src/api/hydra/stages/xbl_signin.rs delete mode 100644 theseus/src/api/hydra/stages/xsts_token.rs create mode 100644 theseus/src/api/minecraft_auth.rs delete mode 100644 theseus/src/launcher/auth.rs delete mode 100644 theseus/src/state/auth_task.rs create mode 100644 theseus/src/state/minecraft_auth.rs delete mode 100644 theseus/src/state/users.rs delete mode 100644 theseus_cli/Cargo.toml delete mode 100644 theseus_cli/src/main.rs delete mode 100644 theseus_cli/src/subcommands/mod.rs delete mode 100644 theseus_cli/src/subcommands/profile.rs delete mode 100644 theseus_cli/src/subcommands/user.rs delete mode 100644 theseus_cli/src/util.rs delete mode 100644 theseus_gui/src/assets/video.mp4 create mode 100644 theseus_gui/src/components/ui/ErrorModal.vue delete mode 100644 theseus_gui/src/components/ui/tutorial/FakeAccountsCard.vue delete mode 100644 theseus_gui/src/components/ui/tutorial/FakeAppBar.vue delete mode 100644 theseus_gui/src/components/ui/tutorial/FakeGridDisplay.vue delete mode 100644 theseus_gui/src/components/ui/tutorial/FakeRowDisplay.vue delete mode 100644 theseus_gui/src/components/ui/tutorial/FakeSearch.vue delete mode 100644 theseus_gui/src/components/ui/tutorial/FakeSettings.vue delete mode 100644 theseus_gui/src/components/ui/tutorial/ImportingCard.vue delete mode 100644 theseus_gui/src/components/ui/tutorial/PreImportScreen.vue delete mode 100644 theseus_gui/src/components/ui/tutorial/TutorialTip.vue create mode 100644 theseus_gui/src/store/error.js diff --git a/.github/workflows/cli-build.yml b/.github/workflows/cli-build.yml deleted file mode 100644 index 49367bd7f..000000000 --- a/.github/workflows/cli-build.yml +++ /dev/null @@ -1,44 +0,0 @@ -name: CLI Build + Lint - -on: - push: - branches: [ master ] - pull_request: -env: - CARGO_TERM_COLOR: always -jobs: - build: - runs-on: ubuntu-latest - defaults: - run: - working-directory: ./theseus_cli - steps: - - name: Checkout - uses: actions/checkout@v3 - - - name: install dependencies - run: | - sudo apt-get update - sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libappindicator3-dev librsvg2-dev patchelf - - - name: Rust setup - uses: dtolnay/rust-toolchain@stable - with: - components: rustfmt, clippy - - - name: Rust cache - uses: swatinem/rust-cache@v2 - with: - workspaces: './src-tauri -> target' - - - uses: actions-rs/cargo@v1 - name: Build program - with: - command: build - args: --bin theseus_cli - - - name: Run Lint - uses: actions-rs/clippy-check@v1 - with: - token: ${{ secrets.GITHUB_TOKEN }} - args: --bin theseus_cli diff --git a/.gitignore b/.gitignore index 363fe7422..a73621ccd 100644 --- a/.gitignore +++ b/.gitignore @@ -100,6 +100,7 @@ fabric.properties # that are supposed to be shared within teams. .idea/* +.vscode/* !.idea/codeStyles !.idea/runConfigurations diff --git a/.vscode/extensions.json b/.vscode/extensions.json deleted file mode 100644 index 2d7ef6459..000000000 --- a/.vscode/extensions.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "recommendations": [ - "tauri-apps.tauri-vscode", - "vunguyentuan.vscode-css-variables", - "stylelint.vscode-stylelint", - "eamodio.gitlens", - "esbenp.prettier-vscode", - "pivaszbs.svelte-autoimport", - "svelte.svelte-vscode", - "ardenivanov.svelte-intellisense" - ] -} \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index 4ce4b41c2..000000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,160 +0,0 @@ -{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in library 'theseus'", - "cargo": { - "args": [ - "test", - "--no-run", - "--lib", - "--package=theseus" - ], - "filter": { - "name": "theseus", - "kind": "lib" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug executable 'theseus_cli'", - "cargo": { - "args": [ - "build", - "--bin=theseus_cli", - "--package=theseus_cli" - ], - "filter": { - "name": "theseus_cli", - "kind": "bin" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in executable 'theseus_cli'", - "cargo": { - "args": [ - "test", - "--no-run", - "--bin=theseus_cli", - "--package=theseus_cli" - ], - "filter": { - "name": "theseus_cli", - "kind": "bin" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug executable 'theseus_playground'", - "cargo": { - "args": [ - "build", - "--bin=theseus_playground", - "--package=theseus_playground" - ], - "filter": { - "name": "theseus_playground", - "kind": "bin" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in executable 'theseus_playground'", - "cargo": { - "args": [ - "test", - "--no-run", - "--bin=theseus_playground", - "--package=theseus_playground" - ], - "filter": { - "name": "theseus_playground", - "kind": "bin" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug executable 'theseus_gui'", - "cargo": { - "args": [ - "build", - "--bin=theseus_gui", - "--package=theseus_gui" - ], - "filter": { - "name": "theseus_gui", - "kind": "bin" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in executable 'theseus_gui'", - "cargo": { - "args": [ - "test", - "--no-run", - "--bin=theseus_gui", - "--package=theseus_gui" - ], - "filter": { - "name": "theseus_gui", - "kind": "bin" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Tauri Development Debug", - "cargo": { - "args": [ - "build", - "--manifest-path=./theseus_gui/src-tauri/Cargo.toml", - "--no-default-features" - ] - }, - "preLaunchTask": "ui:dev" - }, - { - "type": "lldb", - "request": "launch", - "name": "Tauri Production Debug", - "cargo": { - "args": ["build", "--release", "--manifest-path=.theseus_gui/src-tauri/Cargo.toml"] - }, - "preLaunchTask": "ui:build" - } - ] -} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 2d7302c8f..000000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,60 +0,0 @@ -{ - "cssVariables.lookupFiles": [ - "**/*.postcss", - "**/node_modules/omorphia/**/*.postcss" - ], - "cssVariables.blacklistFolders": [ - "**/.git", - "**/.svn", - "**/.hg", - "**/CVS", - "**/.DS_Store", - "**/.git", - "**/bower_components", - "**/tmp", - "**/dist", - "**/tests" - ], - "gitlens.showWelcomeOnInstall": false, - "gitlens.showWhatsNewAfterUpgrades": false, - "gitlens.plusFeatures.enabled": false, - "gitlens.currentLine.enabled": false, - "gitlens.currentLine.pullRequests.enabled": false, - "gitlens.currentLine.scrollable": true, - "gitlens.codeLens.enabled": false, - "gitlens.hovers.enabled": false, - "CSSNavigation.activeCSSFileExtensions": [ - "css", - "postcss" - ], - "CSSNavigation.activeHTMLFileExtensions": [ - "html", - "svelte", - "js", - "ts" - ], - "CSSNavigation.excludeGlobPatterns": [ - "**/bower_components/**", - "**/vendor/**", - "**/coverage/**" - ], - "CSSNavigation.alwaysIncludeGlobPatterns": [ - "./theseus_gui/node_modules/omorphia/**/*.postcss" - ], - "html-css-class-completion.HTMLLanguages": [ - "html", - "svelte" - ], - "html-css-class-completion.includeGlobPattern": "**/*.{postcss,svelte}", - "html-css-class-completion.CSSLanguages": [ - "postcss", - ], - "svelte.enable-ts-plugin": true, - "svelte.ask-to-enable-ts-plugin": false, - "svelte.plugin.css.diagnostics.enable": false, - "svelte.plugin.svelte.diagnostics.enable": false, - "rust-analyzer.linkedProjects": [ - "./theseus/Cargo.toml" - ], - "rust-analyzer.showUnlinkedFileNotification": false, -} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json deleted file mode 100644 index 8f4a751ee..000000000 --- a/.vscode/tasks.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - // See https://go.microsoft.com/fwlink/?LinkId=733558 - // for the documentation about the tasks.json format - "version": "2.0.0", - "tasks": [ - { - "label": "ui:dev", - "type": "shell", - // `dev` keeps running in the background - // ideally you should also configure a `problemMatcher` - // see https://code.visualstudio.com/docs/editor/tasks#_can-a-background-task-be-used-as-a-prelaunchtask-in-launchjson - "isBackground": true, - // change this to your `beforeDevCommand`: - "command": "yarn", - "args": ["dev"], - "options": { - "cwd": "${workspaceFolder}/theseus_gui" - } - }, - { - "label": "ui:build", - "type": "shell", - // change this to your `beforeBuildCommand`: - "command": "yarn", - "args": ["build"], - "options": { - "cwd": "${workspaceFolder}/theseus_gui" - } - } - ] - -} \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 1cdf45d53..29c5a80c3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -69,40 +69,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.81" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" - -[[package]] -name = "argh" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7af5ba06967ff7214ce4c7419c7d185be7ecd6cc4965a8f6e1d8ce0398aad219" -dependencies = [ - "argh_derive", - "argh_shared", -] - -[[package]] -name = "argh_derive" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56df0aeedf6b7a2fc67d06db35b09684c3e8da0c95f8f27685cb17e08413d87a" -dependencies = [ - "argh_shared", - "proc-macro2", - "quote", - "syn 2.0.58", -] - -[[package]] -name = "argh_shared" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5693f39141bda5760ecc4111ab08da40565d1771038c4a0250f03457ec707531" -dependencies = [ - "serde", -] +checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519" [[package]] name = "async-broadcast" @@ -147,9 +116,9 @@ dependencies = [ [[package]] name = "async-executor" -version = "1.9.1" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10b3e585719c2358d2660232671ca8ca4ddb4be4ce8a1842d6c2dc8685303316" +checksum = "5f98c37cf288e302c16ef6c8472aad1e034c6c84ce5ea7b8101c98eb4a802fee" dependencies = [ "async-lock 3.3.0", "async-task", @@ -284,9 +253,9 @@ checksum = "fbb36e985947064623dbd357f727af08ffd077f93d696782f3c56365fa2e2799" [[package]] name = "async-trait" -version = "0.1.79" +version = "0.1.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507401cad91ec6a857ed5513a2073c82a9b9048762b885bb98655b306964681" +checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" dependencies = [ "proc-macro2", "quote", @@ -376,6 +345,12 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + [[package]] name = "base64" version = "0.13.1" @@ -504,15 +479,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.15.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa" - -[[package]] -name = "bytecount" -version = "0.6.7" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1e5f035d16fc623ae5f74981db80a439803888314e3a555fd6f04acd51a3205" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "bytemuck" @@ -592,9 +561,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.90" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5" +checksum = "2678b2e3449475e95b0aa6f9b506a28e61b3dc8996592b983695e8ebb58a8b41" dependencies = [ "jobserver", "libc", @@ -628,9 +597,9 @@ dependencies = [ [[package]] name = "cfg-expr" -version = "0.15.7" +version = "0.15.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa50868b64a9a6fda9d593ce778849ea8715cd2a3d2cc17ffdb4a2f2f2f1961d" +checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" dependencies = [ "smallvec", "target-lexicon", @@ -719,33 +688,6 @@ dependencies = [ "objc", ] -[[package]] -name = "color-eyre" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55146f5e46f237f7423d74111267d4597b59b0dad0ffaf7303bce9945d843ad5" -dependencies = [ - "backtrace", - "color-spantrace", - "eyre", - "indenter", - "once_cell", - "owo-colors", - "tracing-error", -] - -[[package]] -name = "color-spantrace" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd6be1b2a7e382e2b98b43b2adcca6bb0e465af0bdd38123873ae61eb17a72c2" -dependencies = [ - "once_cell", - "owo-colors", - "tracing-core", - "tracing-error", -] - [[package]] name = "color_quant" version = "1.1.0" @@ -754,9 +696,9 @@ checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" [[package]] name = "combine" -version = "4.6.6" +version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35ed6e9d84f0b51a7f52daf1c7d71dd136fd7a3f41a8462b8cdb8c78d920fad4" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" dependencies = [ "bytes", "memchr", @@ -784,6 +726,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + [[package]] name = "constant_time_eq" version = "0.1.5" @@ -925,6 +873,18 @@ version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + [[package]] name = "crypto-common" version = "0.1.6" @@ -1046,6 +1006,17 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83ace6c86376be0b6cdcf3fb41882e81d94b31587573d1cfa9d01cd06bba210d" +[[package]] +name = "der" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + [[package]] name = "deranged" version = "0.3.11" @@ -1080,19 +1051,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "dialoguer" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "658bce805d770f407bc62102fca7c2c64ceef2fbcb2b8bd19d2765ce093980de" -dependencies = [ - "console", - "shell-words", - "tempfile", - "thiserror", - "zeroize", -] - [[package]] name = "digest" version = "0.10.7" @@ -1100,6 +1058,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", + "const-oid", "crypto-common", "subtle", ] @@ -1185,12 +1144,46 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest", + "elliptic-curve", + "rfc6979", + "signature", + "spki", +] + [[package]] name = "either" version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest", + "ff", + "generic-array", + "group", + "pem-rfc7468", + "pkcs8", + "rand_core 0.6.4", + "sec1", + "subtle", + "zeroize", +] + [[package]] name = "embed-resource" version = "2.4.2" @@ -1219,9 +1212,9 @@ checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" [[package]] name = "encoding_rs" -version = "0.8.33" +version = "0.8.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" +checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" dependencies = [ "cfg-if", ] @@ -1322,16 +1315,6 @@ dependencies = [ "pin-project-lite", ] -[[package]] -name = "eyre" -version = "0.6.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" -dependencies = [ - "indenter", - "once_cell", -] - [[package]] name = "fastrand" version = "1.9.0" @@ -1356,6 +1339,16 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "ff" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "field-offset" version = "0.3.6" @@ -1709,6 +1702,7 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] @@ -1724,9 +1718,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a06fddc2749e0528d2813f95e050e87e52c8cbbae56223b9babf73b3e53b0cc6" +checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" dependencies = [ "cfg-if", "libc", @@ -1855,6 +1849,17 @@ dependencies = [ "scroll", ] +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "gtk" version = "0.15.5" @@ -2280,12 +2285,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "indenter" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" - [[package]] name = "indexmap" version = "1.9.3" @@ -2471,9 +2470,9 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" [[package]] name = "jobserver" -version = "0.1.28" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab46a6e9526ddef3ae7f787c06f0f2600639ba80ea3eade3d8e670a2230f51d6" +checksum = "f08474e32172238f2827bd160c67871cdb2801430f65c3979184dc362e3ca118" dependencies = [ "libc", ] @@ -3166,10 +3165,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] -name = "owo-colors" -version = "3.5.0" +name = "p256" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] [[package]] name = "pango" @@ -3196,23 +3201,6 @@ dependencies = [ "system-deps 6.2.2", ] -[[package]] -name = "papergrid" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ad43c07024ef767f9160710b3a6773976194758c7919b17e63b863db0bdf7fb" -dependencies = [ - "bytecount", - "fnv", - "unicode-width", -] - -[[package]] -name = "paris" -version = "1.5.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fecab3723493c7851f292cb060f3ee1c42f19b8d749345d0d7eaf3fd19aa62d" - [[package]] name = "parking" version = "2.2.0" @@ -3277,6 +3265,15 @@ dependencies = [ "sha2", ] +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + [[package]] name = "percent-encoding" version = "2.3.1" @@ -3460,6 +3457,16 @@ dependencies = [ "futures-io", ] +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + [[package]] name = "pkg-config" version = "0.3.30" @@ -3554,6 +3561,15 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + [[package]] name = "proc-macro-crate" version = "1.3.1" @@ -3624,9 +3640,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.35" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] @@ -3691,7 +3707,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.13", + "getrandom 0.2.14", ] [[package]] @@ -3762,7 +3778,7 @@ version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" dependencies = [ - "getrandom 0.2.13", + "getrandom 0.2.14", "libredox", "thiserror", ] @@ -3879,7 +3895,7 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", - "rustls-pemfile 2.1.1", + "rustls-pemfile 2.1.2", "serde", "serde_json", "serde_urlencoded", @@ -3903,6 +3919,16 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "194d8e591e405d1eecf28819740abed6d719d1a2db87fc0bcdedee9a26d55560" +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + [[package]] name = "rfd" version = "0.10.0" @@ -3980,11 +4006,11 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f48172685e6ff52a556baa527774f61fcaa884f59daf3375c62a3f1cd2549dab" +checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" dependencies = [ - "base64 0.21.7", + "base64 0.22.0", "rustls-pki-types", ] @@ -3996,9 +4022,9 @@ checksum = "ecd36cc4259e3e4514335c4a138c6b43171a8d61d8f5c9348f9fc7529416f247" [[package]] name = "rustversion" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" +checksum = "80af6f9131f277a45a3fba6ce8e2258037bb0477a67e610d3c1fe046ab31de47" [[package]] name = "ryu" @@ -4056,6 +4082,20 @@ dependencies = [ "syn 2.0.58", ] +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + [[package]] name = "security-framework" version = "2.10.0" @@ -4272,9 +4312,9 @@ dependencies = [ [[package]] name = "serde_repr" -version = "0.1.18" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b2e6b945e9d3df726b65d6ee24060aff8e3533d431f677a9695db04eff9dfdb" +checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" dependencies = [ "proc-macro2", "quote", @@ -4410,12 +4450,6 @@ dependencies = [ "lazy_static", ] -[[package]] -name = "shell-words" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" - [[package]] name = "signal-hook-registry" version = "1.4.1" @@ -4425,6 +4459,16 @@ dependencies = [ "libc", ] +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core 0.6.4", +] + [[package]] name = "simd-adler32" version = "0.3.7" @@ -4511,6 +4555,16 @@ dependencies = [ "system-deps 5.0.0", ] +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -4623,9 +4677,9 @@ dependencies = [ [[package]] name = "sysinfo" -version = "0.30.8" +version = "0.30.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b1a378e48fb3ce3a5cf04359c456c9c98ff689bcf1c1bc6e6a31f247686f275" +checksum = "26d7c217777061d5a2d652aea771fb9ba98b6dade657204b08c4b9604d11555b" dependencies = [ "cfg-if", "core-foundation-sys", @@ -4676,37 +4730,13 @@ version = "6.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" dependencies = [ - "cfg-expr 0.15.7", + "cfg-expr 0.15.8", "heck 0.5.0", "pkg-config", "toml 0.8.12", "version-compare 0.2.0", ] -[[package]] -name = "tabled" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c998b0c8b921495196a48aabaf1901ff28be0760136e31604f7967b0792050e" -dependencies = [ - "papergrid", - "tabled_derive", - "unicode-width", -] - -[[package]] -name = "tabled_derive" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c138f99377e5d653a371cdad263615634cfc8467685dfe8e73e2b8e98f44b17" -dependencies = [ - "heck 0.4.1", - "proc-macro-error", - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "tao" version = "0.16.8" @@ -4918,7 +4948,7 @@ dependencies = [ [[package]] name = "tauri-plugin-single-instance" version = "0.0.0" -source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v1#cf832fe106cf272916cfdda63235c139dc68171a" +source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v1#69ee862fb7f9701f8fca9a5235aeec0eb7714b11" dependencies = [ "log", "serde", @@ -4932,7 +4962,7 @@ dependencies = [ [[package]] name = "tauri-plugin-window-state" version = "0.1.1" -source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v1#cf832fe106cf272916cfdda63235c139dc68171a" +source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v1#69ee862fb7f9701f8fca9a5235aeec0eb7714b11" dependencies = [ "bincode 1.3.3", "bitflags 2.5.0", @@ -5054,6 +5084,8 @@ dependencies = [ "async-recursion", "async-tungstenite", "async_zip", + "base64 0.22.0", + "byteorder", "bytes", "chrono", "daedalus", @@ -5066,7 +5098,9 @@ dependencies = [ "lazy_static", "notify", "notify-debouncer-mini", + "p256", "paste", + "rand 0.8.5", "regex", "reqwest 0.12.3", "serde", @@ -5095,33 +5129,6 @@ dependencies = [ "zip", ] -[[package]] -name = "theseus_cli" -version = "0.6.3" -dependencies = [ - "argh", - "color-eyre", - "daedalus", - "dialoguer", - "dirs", - "dunce", - "eyre", - "futures", - "paris", - "tabled", - "theseus", - "tokio", - "tokio-stream", - "tracing", - "tracing-error", - "tracing-futures", - "tracing-subscriber", - "url", - "uuid 1.8.0", - "webbrowser", - "winreg 0.52.0", -] - [[package]] name = "theseus_gui" version = "0.6.3" @@ -5223,9 +5230,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.34" +version = "0.3.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ "deranged", "itoa 1.0.11", @@ -5244,9 +5251,9 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.17" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ba3a3ef41e6672a2f0f001392bb5dcd3ff0a9992d618ca761a11c3121547774" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" dependencies = [ "num-conv", "time-core", @@ -5404,7 +5411,7 @@ dependencies = [ "serde", "serde_spanned", "toml_datetime", - "winnow 0.6.5", + "winnow 0.6.6", ] [[package]] @@ -5490,16 +5497,6 @@ dependencies = [ "tracing-subscriber", ] -[[package]] -name = "tracing-futures" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" -dependencies = [ - "pin-project", - "tracing", -] - [[package]] name = "tracing-log" version = "0.2.0" @@ -5676,7 +5673,7 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" dependencies = [ - "getrandom 0.2.13", + "getrandom 0.2.14", ] [[package]] @@ -5685,7 +5682,7 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0" dependencies = [ - "getrandom 0.2.13", + "getrandom 0.2.14", "serde", ] @@ -5885,9 +5882,9 @@ dependencies = [ [[package]] name = "webbrowser" -version = "0.8.14" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd595fb70f33583ac61644820ebc144a26c96028b625b96cafcd861f4743fbc8" +checksum = "db67ae75a9405634f5882791678772c94ff5f16a66535aae186e26aa0841fc8b" dependencies = [ "core-foundation", "home", @@ -6419,9 +6416,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.6.5" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dffa400e67ed5a4dd237983829e66475f0a4a26938c4b04c21baede6262215b8" +checksum = "f0c976aaaa0e1f90dbb21e9587cdaf1d9679a1cde8875c0d6bd83ab96a208352" dependencies = [ "memchr", ] diff --git a/Cargo.toml b/Cargo.toml index 7fea9d1ad..fe2ab1001 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,6 @@ members = [ "theseus", - "theseus_cli", "theseus_playground", "theseus_gui/src-tauri", "theseus_macros" diff --git a/theseus.iml b/theseus.iml deleted file mode 100644 index fbdd85c4d..000000000 --- a/theseus.iml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/theseus/Cargo.toml b/theseus/Cargo.toml index 33639b658..2b23e8893 100644 --- a/theseus/Cargo.toml +++ b/theseus/Cargo.toml @@ -35,7 +35,7 @@ sysinfo = "0.30.8" thiserror = "1.0" tracing = "0.1.37" -tracing-subscriber = { version = "0.3.18", features = ["chrono"] } +tracing-subscriber = { version = "0.3.18", features = ["chrono", "env-filter"] } tracing-error = "0.2.0" tracing-appender = "0.2.3" @@ -61,6 +61,11 @@ whoami = "1.4.0" discord-rich-presence = "0.2.3" +p256 = { version = "0.13.2", features = ["ecdsa"] } +rand = "0.8" +byteorder = "1.5.0" +base64 = "0.22.0" + [target.'cfg(windows)'.dependencies] winreg = "0.52.0" diff --git a/theseus/src/api/auth.rs b/theseus/src/api/auth.rs deleted file mode 100644 index 36c80816f..000000000 --- a/theseus/src/api/auth.rs +++ /dev/null @@ -1,132 +0,0 @@ -//! Authentication flow interface -use crate::{ - hydra::{self, init::DeviceLoginSuccess}, - launcher::auth as inner, - State, -}; -use chrono::Utc; - -use crate::state::AuthTask; -pub use inner::Credentials; - -/// Authenticate a user with Hydra - part 1 -/// This begins the authentication flow quasi-synchronously, returning a URL -/// This can be used in conjunction with 'authenticate_await_complete_flow' -/// to call authenticate and call the flow from the frontend. -/// Visit the URL in a browser, then call and await 'authenticate_await_complete_flow'. -pub async fn authenticate_begin_flow() -> crate::Result { - let url = AuthTask::begin_auth().await?; - Ok(url) -} - -/// Authenticate a user with Hydra - part 2 -/// This completes the authentication flow quasi-synchronously, returning the credentials -/// This can be used in conjunction with 'authenticate_begin_flow' -/// to call authenticate and call the flow from the frontend. -pub async fn authenticate_await_complete_flow() -> crate::Result { - let credentials = AuthTask::await_auth_completion().await?; - Ok(credentials) -} - -/// Cancels the active authentication flow -pub async fn cancel_flow() -> crate::Result<()> { - AuthTask::cancel().await -} - -/// Refresh some credentials using Hydra, if needed -/// This is the primary desired way to get credentials, as it will also refresh them. -#[tracing::instrument] -#[theseus_macros::debug_pin] -pub async fn refresh(user: uuid::Uuid) -> crate::Result { - let state = State::get().await?; - let mut users = state.users.write().await; - - let mut credentials = users.get(user).ok_or_else(|| { - crate::ErrorKind::OtherError( - "You are not logged in with a Minecraft account!".to_string(), - ) - .as_error() - })?; - - let offline = *state.offline.read().await; - - if !offline { - let fetch_semaphore: &crate::util::fetch::FetchSemaphore = - &state.fetch_semaphore; - if Utc::now() > credentials.expires - && inner::refresh_credentials(&mut credentials, fetch_semaphore) - .await - .is_err() - { - users.remove(credentials.id).await?; - - return Err(crate::ErrorKind::OtherError( - "Please re-authenticate with your Minecraft account!" - .to_string(), - ) - .as_error()); - } - - // Update player info from bearer token - let player_info = - hydra::stages::player_info::fetch_info(&credentials.access_token) - .await - .map_err(|_err| { - crate::ErrorKind::HydraError( - "No Minecraft account for your profile. Please try again or contact support in our Discord for help!".to_string(), - ) - })?; - - credentials.username = player_info.name; - users.insert(&credentials).await?; - } - - Ok(credentials) -} - -/// Remove a user account from the database -#[tracing::instrument] -pub async fn remove_user(user: uuid::Uuid) -> crate::Result<()> { - let state = State::get().await?; - let mut users = state.users.write().await; - - if state.settings.read().await.default_user == Some(user) { - let mut settings = state.settings.write().await; - settings.default_user = users.0.values().next().map(|it| it.id); - } - - users.remove(user).await?; - Ok(()) -} - -/// Check if a user exists in Theseus -#[tracing::instrument] -pub async fn has_user(user: uuid::Uuid) -> crate::Result { - let state = State::get().await?; - let users = state.users.read().await; - - Ok(users.contains(user)) -} - -/// Get a copy of the list of all user credentials -#[tracing::instrument] -pub async fn users() -> crate::Result> { - let state = State::get().await?; - let users = state.users.read().await; - Ok(users.0.values().cloned().collect()) -} - -/// Get a specific user by user ID -/// Prefer to use 'refresh' instead of this function -#[tracing::instrument] -pub async fn get_user(user: uuid::Uuid) -> crate::Result { - let state = State::get().await?; - let users = state.users.read().await; - let user = users.get(user).ok_or_else(|| { - crate::ErrorKind::OtherError(format!( - "Tried to get nonexistent user with ID {user}" - )) - .as_error() - })?; - Ok(user) -} diff --git a/theseus/src/api/hydra/complete.rs b/theseus/src/api/hydra/complete.rs deleted file mode 100644 index e42d1f375..000000000 --- a/theseus/src/api/hydra/complete.rs +++ /dev/null @@ -1,84 +0,0 @@ -//! Main authentication flow for Hydra - -use serde::Deserialize; - -use crate::prelude::Credentials; - -use super::stages::{ - bearer_token, player_info, poll_response, xbl_signin, xsts_token, -}; - -#[derive(Debug, Deserialize)] -pub struct OauthFailure { - pub error: String, -} - -pub struct SuccessfulLogin { - pub name: String, - pub icon: String, - pub token: String, - pub refresh_token: String, - pub expires_after: i64, -} - -pub async fn wait_finish(device_code: String) -> crate::Result { - // Loop, polling for response from Microsoft - let oauth = poll_response::poll_response(device_code).await?; - - // Get xbl token from oauth token - let xbl_token = xbl_signin::login_xbl(&oauth.access_token).await?; - - // Get xsts token from xbl token - let xsts_response = xsts_token::fetch_token(&xbl_token.token).await?; - - match xsts_response { - xsts_token::XSTSResponse::Unauthorized(err) => { - Err(crate::ErrorKind::HydraError(format!( - "Error getting XBox Live token: {}", - err - )) - .as_error()) - } - xsts_token::XSTSResponse::Success { token: xsts_token } => { - // Get xsts bearer token from xsts token - let (bearer_token, expires_in) = - bearer_token::fetch_bearer(&xsts_token, &xbl_token.uhs) - .await - .map_err(|err| { - crate::ErrorKind::HydraError(format!( - "Error getting bearer token: {}", - err - )) - })?; - - // Get player info from bearer token - let player_info = player_info::fetch_info(&bearer_token).await.map_err(|_err| { - crate::ErrorKind::HydraError("No Minecraft account for profile. Make sure you own the game and have set a username through the official Minecraft launcher." - .to_string()) - })?; - - // Create credentials - let credentials = Credentials::new( - uuid::Uuid::parse_str(&player_info.id)?, // get uuid from player_info.id which is a String - player_info.name, - bearer_token, - oauth.refresh_token, - chrono::Utc::now() + chrono::Duration::seconds(expires_in), - ); - - // Put credentials into state - let state = crate::State::get().await?; - { - let mut users = state.users.write().await; - users.insert(&credentials).await?; - } - - if state.settings.read().await.default_user.is_none() { - let mut settings = state.settings.write().await; - settings.default_user = Some(credentials.id); - } - - Ok(credentials) - } - } -} diff --git a/theseus/src/api/hydra/init.rs b/theseus/src/api/hydra/init.rs deleted file mode 100644 index e0f34d7ad..000000000 --- a/theseus/src/api/hydra/init.rs +++ /dev/null @@ -1,47 +0,0 @@ -//! Login route for Hydra, redirects to the Microsoft login page before going to the redirect route -use serde::{Deserialize, Serialize}; - -use crate::{hydra::MicrosoftError, util::fetch::REQWEST_CLIENT}; - -use super::{stages::auth_retry, MICROSOFT_CLIENT_ID}; - -#[derive(Serialize, Deserialize, Debug)] -pub struct DeviceLoginSuccess { - pub device_code: String, - pub user_code: String, - pub verification_uri: String, - pub expires_in: u64, - pub interval: u64, - pub message: String, -} - -pub async fn init() -> crate::Result { - // Get the initial URL - // Get device code - // Define the parameters - - // urlencoding::encode("XboxLive.signin offline_access")); - let resp = auth_retry(|| REQWEST_CLIENT.get("https://login.microsoftonline.com/consumers/oauth2/v2.0/devicecode") - .header("Content-Length", "0") - .query(&[ - ("client_id", MICROSOFT_CLIENT_ID), - ( - "scope", - "XboxLive.signin XboxLive.offline_access profile openid email", - ), - ]) - .send() - ).await?; - - match resp.status() { - reqwest::StatusCode::OK => Ok(resp.json().await?), - _ => { - let microsoft_error = resp.json::().await?; - Err(crate::ErrorKind::HydraError(format!( - "Error from Microsoft: {:?}", - microsoft_error.error_description - )) - .into()) - } - } -} diff --git a/theseus/src/api/hydra/mod.rs b/theseus/src/api/hydra/mod.rs deleted file mode 100644 index a6be05718..000000000 --- a/theseus/src/api/hydra/mod.rs +++ /dev/null @@ -1,15 +0,0 @@ -pub mod complete; -pub mod init; -pub mod refresh; -pub(crate) mod stages; - -use serde::Deserialize; - -const MICROSOFT_CLIENT_ID: &str = "c4502edb-87c6-40cb-b595-64a280cf8906"; - -#[derive(Deserialize)] -pub struct MicrosoftError { - pub error: String, - pub error_description: String, - pub error_codes: Vec, -} diff --git a/theseus/src/api/hydra/refresh.rs b/theseus/src/api/hydra/refresh.rs deleted file mode 100644 index b8834ecc5..000000000 --- a/theseus/src/api/hydra/refresh.rs +++ /dev/null @@ -1,69 +0,0 @@ -use std::collections::HashMap; - -use reqwest::StatusCode; -use serde::Deserialize; - -use crate::{ - hydra::{MicrosoftError, MICROSOFT_CLIENT_ID}, - util::fetch::REQWEST_CLIENT, -}; - -use super::stages::auth_retry; - -#[derive(Debug, Deserialize)] -pub struct OauthSuccess { - pub token_type: String, - pub scope: String, - pub expires_in: i64, - pub access_token: String, - pub refresh_token: String, -} - -pub async fn refresh(refresh_token: String) -> crate::Result { - let mut params = HashMap::new(); - params.insert("grant_type", "refresh_token"); - params.insert("client_id", MICROSOFT_CLIENT_ID); - params.insert("refresh_token", &refresh_token); - params.insert( - "redirect_uri", - "https://login.microsoftonline.com/common/oauth2/nativeclient", - ); - - // Poll the URL in a loop until we are successful. - // On an authorization_pending response, wait 5 seconds and try again. - let resp = - auth_retry(|| { - REQWEST_CLIENT - .post("https://login.microsoftonline.com/consumers/oauth2/v2.0/token") - .header("Content-Type", "application/x-www-form-urlencoded") - .form(¶ms) - .send() - }) - .await?; - - match resp.status() { - StatusCode::OK => { - let oauth = resp.json::().await.map_err(|err| { - crate::ErrorKind::HydraError(format!( - "Could not decipher successful response: {}", - err - )) - })?; - Ok(oauth) - } - _ => { - let failure = - resp.json::().await.map_err(|err| { - crate::ErrorKind::HydraError(format!( - "Could not decipher failure response: {}", - err - )) - })?; - Err(crate::ErrorKind::HydraError(format!( - "Error refreshing token: {}", - failure.error - )) - .as_error()) - } - } -} diff --git a/theseus/src/api/hydra/stages/bearer_token.rs b/theseus/src/api/hydra/stages/bearer_token.rs deleted file mode 100644 index 4c6600189..000000000 --- a/theseus/src/api/hydra/stages/bearer_token.rs +++ /dev/null @@ -1,42 +0,0 @@ -use serde::Deserialize; -use serde_json::json; - -use super::auth_retry; - -const MCSERVICES_AUTH_URL: &str = - "https://api.minecraftservices.com/authentication/login_with_xbox"; - -#[derive(Deserialize)] -pub struct BearerTokenResponse { - access_token: String, - expires_in: i64, -} - -#[tracing::instrument] -pub async fn fetch_bearer( - token: &str, - uhs: &str, -) -> crate::Result<(String, i64)> { - let body = auth_retry(|| { - let client = reqwest::Client::new(); - client - .post(MCSERVICES_AUTH_URL) - .header("Accept", "application/json") - .json(&json!({ - "identityToken": format!("XBL3.0 x={};{}", uhs, token), - })) - .send() - }) - .await? - .text() - .await?; - - serde_json::from_str::(&body) - .map(|x| (x.access_token, x.expires_in)) - .map_err(|_| { - crate::ErrorKind::HydraError(format!( - "Response didn't contain valid bearer token. body: {body}" - )) - .into() - }) -} diff --git a/theseus/src/api/hydra/stages/mod.rs b/theseus/src/api/hydra/stages/mod.rs deleted file mode 100644 index b24421707..000000000 --- a/theseus/src/api/hydra/stages/mod.rs +++ /dev/null @@ -1,37 +0,0 @@ -//! MSA authentication stages - -use futures::Future; -use reqwest::Response; - -const RETRY_COUNT: usize = 9; // Does command 3 times -const RETRY_WAIT: std::time::Duration = std::time::Duration::from_secs(2); - -pub mod bearer_token; -pub mod player_info; -pub mod poll_response; -pub mod xbl_signin; -pub mod xsts_token; - -#[tracing::instrument(skip(reqwest_request))] -pub async fn auth_retry( - reqwest_request: impl Fn() -> F, -) -> crate::Result -where - F: Future>, -{ - let mut resp = reqwest_request().await?; - for i in 0..RETRY_COUNT { - if resp.status().is_success() { - break; - } - tracing::debug!( - "Request failed with status code {}, retrying...", - resp.status() - ); - if i < RETRY_COUNT - 1 { - tokio::time::sleep(RETRY_WAIT).await; - } - resp = reqwest_request().await?; - } - Ok(resp) -} diff --git a/theseus/src/api/hydra/stages/player_info.rs b/theseus/src/api/hydra/stages/player_info.rs deleted file mode 100644 index a78ddcdb9..000000000 --- a/theseus/src/api/hydra/stages/player_info.rs +++ /dev/null @@ -1,46 +0,0 @@ -//! Fetch player info for display -use serde::Deserialize; - -use crate::util::fetch::REQWEST_CLIENT; - -use super::auth_retry; - -const PROFILE_URL: &str = "https://api.minecraftservices.com/minecraft/profile"; - -#[derive(Deserialize)] -pub struct PlayerInfo { - pub id: String, - pub name: String, -} - -impl Default for PlayerInfo { - fn default() -> Self { - Self { - id: "606e2ff0ed7748429d6ce1d3321c7838".to_string(), - name: String::from("Unknown"), - } - } -} - -#[tracing::instrument] -pub async fn fetch_info(token: &str) -> crate::Result { - auth_retry(|| { - REQWEST_CLIENT - .get("https://api.minecraftservices.com/entitlements/mcstore") - .bearer_auth(token) - .send() - }) - .await?; - - let response = auth_retry(|| { - REQWEST_CLIENT - .get(PROFILE_URL) - .header(reqwest::header::AUTHORIZATION, format!("Bearer {token}")) - .send() - }) - .await?; - - let resp = response.error_for_status()?.json().await?; - - Ok(resp) -} diff --git a/theseus/src/api/hydra/stages/poll_response.rs b/theseus/src/api/hydra/stages/poll_response.rs deleted file mode 100644 index 102d098b0..000000000 --- a/theseus/src/api/hydra/stages/poll_response.rs +++ /dev/null @@ -1,99 +0,0 @@ -use std::collections::HashMap; - -use reqwest::StatusCode; -use serde::Deserialize; - -use crate::{ - hydra::{MicrosoftError, MICROSOFT_CLIENT_ID}, - util::fetch::REQWEST_CLIENT, -}; - -use super::auth_retry; - -#[derive(Debug, Deserialize)] -pub struct OauthSuccess { - pub token_type: String, - pub scope: String, - pub expires_in: i64, - pub access_token: String, - pub refresh_token: String, -} - -#[tracing::instrument] -pub async fn poll_response(device_code: String) -> crate::Result { - let mut params = HashMap::new(); - params.insert("grant_type", "urn:ietf:params:oauth:grant-type:device_code"); - params.insert("client_id", MICROSOFT_CLIENT_ID); - params.insert("device_code", &device_code); - params.insert( - "scope", - "XboxLive.signin XboxLive.offline_access profile openid email", - ); - - // Poll the URL in a loop until we are successful. - // On an authorization_pending response, wait 5 seconds and try again. - loop { - let resp = auth_retry(|| { - REQWEST_CLIENT - .post( - "https://login.microsoftonline.com/consumers/oauth2/v2.0/token", - ) - .form(¶ms) - .send() - }) - .await?; - - match resp.status() { - StatusCode::OK => { - let oauth = - resp.json::().await.map_err(|err| { - crate::ErrorKind::HydraError(format!( - "Could not decipher successful response: {}", - err - )) - })?; - return Ok(oauth); - } - _ => { - let failure = - resp.json::().await.map_err(|err| { - crate::ErrorKind::HydraError(format!( - "Could not decipher failure response: {}", - err - )) - })?; - match failure.error.as_str() { - "authorization_pending" => { - tokio::time::sleep(std::time::Duration::from_secs(2)) - .await; - } - "authorization_declined" => { - return Err(crate::ErrorKind::HydraError( - "Authorization declined".to_string(), - ) - .as_error()); - } - "expired_token" => { - return Err(crate::ErrorKind::HydraError( - "Device code expired".to_string(), - ) - .as_error()); - } - "bad_verification_code" => { - return Err(crate::ErrorKind::HydraError( - "Invalid device code".to_string(), - ) - .as_error()); - } - _ => { - return Err(crate::ErrorKind::HydraError(format!( - "Unknown error: {}", - failure.error - )) - .as_error()); - } - } - } - } - } -} diff --git a/theseus/src/api/hydra/stages/xbl_signin.rs b/theseus/src/api/hydra/stages/xbl_signin.rs deleted file mode 100644 index 6f2a7c025..000000000 --- a/theseus/src/api/hydra/stages/xbl_signin.rs +++ /dev/null @@ -1,59 +0,0 @@ -use serde_json::json; - -use crate::util::fetch::REQWEST_CLIENT; - -use super::auth_retry; - -const XBL_AUTH_URL: &str = "https://user.auth.xboxlive.com/user/authenticate"; - -// Deserialization -pub struct XBLLogin { - pub token: String, - pub uhs: String, -} - -// Impl -#[tracing::instrument] -pub async fn login_xbl(token: &str) -> crate::Result { - let response = auth_retry(|| { - REQWEST_CLIENT - .post(XBL_AUTH_URL) - .header(reqwest::header::ACCEPT, "application/json") - .json(&json!({ - "Properties": { - "AuthMethod": "RPS", - "SiteName": "user.auth.xboxlive.com", - "RpsTicket": format!("d={token}") - }, - "RelyingParty": "http://auth.xboxlive.com", - "TokenType": "JWT" - })) - .send() - }) - .await?; - let body = response.text().await?; - - let json = serde_json::from_str::(&body)?; - let token = Some(&json) - .and_then(|it| it.get("Token")?.as_str().map(String::from)) - .ok_or(crate::ErrorKind::HydraError( - "XBL response didn't contain valid token".to_string(), - ))?; - let uhs = Some(&json) - .and_then(|it| { - it.get("DisplayClaims")? - .get("xui")? - .get(0)? - .get("uhs")? - .as_str() - .map(String::from) - }) - .ok_or( - crate::ErrorKind::HydraError( - "XBL response didn't contain valid user hash".to_string(), - ) - .as_error(), - )?; - - Ok(XBLLogin { token, uhs }) -} diff --git a/theseus/src/api/hydra/stages/xsts_token.rs b/theseus/src/api/hydra/stages/xsts_token.rs deleted file mode 100644 index 80fbd892c..000000000 --- a/theseus/src/api/hydra/stages/xsts_token.rs +++ /dev/null @@ -1,62 +0,0 @@ -use serde_json::json; - -use crate::util::fetch::REQWEST_CLIENT; - -use super::auth_retry; - -const XSTS_AUTH_URL: &str = "https://xsts.auth.xboxlive.com/xsts/authorize"; - -pub enum XSTSResponse { - Unauthorized(String), - Success { token: String }, -} - -#[tracing::instrument] -pub async fn fetch_token(token: &str) -> crate::Result { - let resp = auth_retry(|| { - REQWEST_CLIENT - .post(XSTS_AUTH_URL) - .header(reqwest::header::ACCEPT, "application/json") - .json(&json!({ - "Properties": { - "SandboxId": "RETAIL", - "UserTokens": [ - token - ] - }, - "RelyingParty": "rp://api.minecraftservices.com/", - "TokenType": "JWT" - })) - .send() - }) - .await?; - let status = resp.status(); - - let body = resp.text().await?; - let json = serde_json::from_str::(&body)?; - - if status.is_success() { - Ok(json - .get("Token") - .and_then(|x| x.as_str().map(String::from)) - .map(|it| XSTSResponse::Success { token: it }) - .unwrap_or(XSTSResponse::Unauthorized( - "XSTS response didn't contain valid token!".to_string(), - ))) - } else { - Ok(XSTSResponse::Unauthorized( - #[allow(clippy::unreadable_literal)] - match json.get("XErr").and_then(|x| x.as_i64()) { - Some(2148916238) => { - String::from("This Microsoft account is underage and is not linked to a family.") - }, - Some(2148916235) => { - String::from("XBOX Live/Minecraft is not available in your country.") - }, - Some(2148916233) => String::from("This account does not have a valid XBOX Live profile. Please buy Minecraft and try again!"), - Some(2148916236) | Some(2148916237) => String::from("This account needs adult verification on Xbox page."), - _ => String::from("Unknown error code"), - }, - )) - } -} diff --git a/theseus/src/api/logs.rs b/theseus/src/api/logs.rs index 6a1aa492c..4d6614cda 100644 --- a/theseus/src/api/logs.rs +++ b/theseus/src/api/logs.rs @@ -140,8 +140,14 @@ pub async fn get_output_by_filename( let logs_folder = DirectoryInfo::profile_logs_dir(profile_subpath).await?; let path = logs_folder.join(file_name); - let credentials: Vec = - state.users.read().await.clone().0.into_values().collect(); + let credentials: Vec = state + .users + .read() + .await + .users + .clone() + .into_values() + .collect(); // Load .gz file into String if let Some(ext) = path.extension() { @@ -296,8 +302,14 @@ pub async fn get_generic_live_log_cursor( let output = String::from_utf8_lossy(&buffer).to_string(); // Convert to String let cursor = cursor + bytes_read as u64; // Update cursor - let credentials: Vec = - state.users.read().await.clone().0.into_values().collect(); + let credentials: Vec = state + .users + .read() + .await + .users + .clone() + .into_values() + .collect(); let output = CensoredString::censor(output, &credentials); Ok(LatestLogCursor { cursor, diff --git a/theseus/src/api/minecraft_auth.rs b/theseus/src/api/minecraft_auth.rs new file mode 100644 index 000000000..3813edbb3 --- /dev/null +++ b/theseus/src/api/minecraft_auth.rs @@ -0,0 +1,76 @@ +//! Authentication flow interface +use crate::state::{Credentials, MinecraftLoginFlow}; +use crate::State; + +#[tracing::instrument] +pub async fn begin_login() -> crate::Result { + let state = State::get().await?; + let mut users = state.users.write().await; + + users.login_begin().await +} + +#[tracing::instrument] +pub async fn finish_login( + code: &str, + flow: MinecraftLoginFlow, +) -> crate::Result { + let state = State::get().await?; + let mut users = state.users.write().await; + + users.login_finish(code, flow).await +} + +#[tracing::instrument] +pub async fn get_default_user() -> crate::Result> { + let state = State::get().await?; + let users = state.users.read().await; + Ok(users.default_user) +} + +#[tracing::instrument] +pub async fn set_default_user(user: uuid::Uuid) -> crate::Result<()> { + let user = get_user(user).await?; + let state = State::get().await?; + let mut users = state.users.write().await; + users.default_user = Some(user.id); + users.save().await?; + Ok(()) +} + +/// Remove a user account from the database +#[tracing::instrument] +pub async fn remove_user(user: uuid::Uuid) -> crate::Result<()> { + let state = State::get().await?; + let mut users = state.users.write().await; + users.remove(user).await?; + + Ok(()) +} + +/// Get a copy of the list of all user credentials +#[tracing::instrument] +pub async fn users() -> crate::Result> { + let state = State::get().await?; + let users = state.users.read().await; + Ok(users.users.values().cloned().collect()) +} + +/// Get a specific user by user ID +/// Prefer to use 'refresh' instead of this function +#[tracing::instrument] +pub async fn get_user(user: uuid::Uuid) -> crate::Result { + let state = State::get().await?; + let users = state.users.read().await; + let user = users + .users + .get(&user) + .ok_or_else(|| { + crate::ErrorKind::OtherError(format!( + "Tried to get nonexistent user with ID {user}" + )) + .as_error() + })? + .clone(); + Ok(user) +} diff --git a/theseus/src/api/mod.rs b/theseus/src/api/mod.rs index 37ca4b4d1..97b7991da 100644 --- a/theseus/src/api/mod.rs +++ b/theseus/src/api/mod.rs @@ -1,10 +1,9 @@ //! API for interacting with Theseus -pub mod auth; pub mod handler; -pub mod hydra; pub mod jre; pub mod logs; pub mod metadata; +pub mod minecraft_auth; pub mod mr_auth; pub mod pack; pub mod process; @@ -15,19 +14,19 @@ pub mod tags; pub mod data { pub use crate::state::{ - DirectoryInfo, Hooks, JavaSettings, LinkedData, MemorySettings, - ModLoader, ModrinthCredentials, ModrinthCredentialsResult, - ModrinthProject, ModrinthTeamMember, ModrinthUser, ModrinthVersion, - ProfileMetadata, ProjectMetadata, Settings, Theme, WindowSize, + Credentials, DirectoryInfo, Hooks, JavaSettings, LinkedData, + MemorySettings, ModLoader, ModrinthCredentials, + ModrinthCredentialsResult, ModrinthProject, ModrinthTeamMember, + ModrinthUser, ModrinthVersion, ProfileMetadata, ProjectMetadata, + Settings, Theme, WindowSize, }; } pub mod prelude { pub use crate::{ - auth::{self, Credentials}, data::*, event::CommandPayload, - jre, metadata, pack, process, + jre, metadata, minecraft_auth, pack, process, profile::{self, create, Profile}, settings, state::JavaGlobals, diff --git a/theseus/src/api/profile/mod.rs b/theseus/src/api/profile/mod.rs index 2a15c0470..47b83ccfa 100644 --- a/theseus/src/api/profile/mod.rs +++ b/theseus/src/api/profile/mod.rs @@ -8,12 +8,13 @@ use crate::pack::install_from::{ EnvType, PackDependency, PackFile, PackFileHash, PackFormat, }; use crate::prelude::{JavaVersion, ProfilePathId, ProjectPathId}; -use crate::state::{InnerProjectPathUnix, ProjectMetadata, SideType}; +use crate::state::{ + Credentials, InnerProjectPathUnix, ProjectMetadata, SideType, +}; use crate::util::fetch; use crate::util::io::{self, IOError}; use crate::{ - auth::{self, refresh}, event::{emit::emit_profile, ProfilePayloadType}, state::MinecraftChild, }; @@ -745,20 +746,16 @@ pub async fn run( let state = State::get().await?; // Get default account and refresh credentials (preferred way to log in) - let default_account = state.settings.read().await.default_user; - let credentials = if let Some(default_account) = default_account { - refresh(default_account).await? - } else { - // If no default account, try to use a logged in account - let users = auth::users().await?; - let last_account = users.first(); - if let Some(last_account) = last_account { - refresh(last_account.id).await? - } else { - return Err(crate::ErrorKind::NoCredentialsError.as_error()); - } + let default_account = { + let mut write = state.users.write().await; + + write + .get_default_credential() + .await? + .ok_or_else(|| crate::ErrorKind::NoCredentialsError.as_error())? }; - run_credentials(path, &credentials).await + + run_credentials(path, &default_account).await } /// Run Minecraft using a profile, and credentials for authentication @@ -767,7 +764,7 @@ pub async fn run( #[theseus_macros::debug_pin] pub async fn run_credentials( path: &ProfilePathId, - credentials: &auth::Credentials, + credentials: &Credentials, ) -> crate::Result>> { let state = State::get().await?; let settings = state.settings.read().await; diff --git a/theseus/src/error.rs b/theseus/src/error.rs index 2ea915c84..abae404c5 100644 --- a/theseus/src/error.rs +++ b/theseus/src/error.rs @@ -25,11 +25,10 @@ pub enum ErrorKind { #[error("Metadata error: {0}")] MetadataError(#[from] daedalus::Error), - #[error("Minecraft authentication Hydra error: {0}")] - HydraError(String), - - #[error("Minecraft authentication task error: {0}")] - AuthTaskError(#[from] crate::state::AuthTaskError), + #[error("Minecraft authentication error: {0}")] + MinecraftAuthenticationError( + #[from] crate::state::MinecraftAuthenticationError, + ), #[error("I/O error: {0}")] IOError(#[from] util::io::IOError), diff --git a/theseus/src/event/mod.rs b/theseus/src/event/mod.rs index 3dd77ef50..0738aea83 100644 --- a/theseus/src/event/mod.rs +++ b/theseus/src/event/mod.rs @@ -43,7 +43,7 @@ impl EventState { })) }) .await - .map(Arc::clone) + .cloned() } #[cfg(feature = "tauri")] diff --git a/theseus/src/launcher/args.rs b/theseus/src/launcher/args.rs index a49c2b748..c36a37489 100644 --- a/theseus/src/launcher/args.rs +++ b/theseus/src/launcher/args.rs @@ -1,6 +1,6 @@ //! Minecraft CLI argument logic -use super::auth::Credentials; use crate::launcher::parse_rules; +use crate::state::Credentials; use crate::{ state::{MemorySettings, WindowSize}, util::{io::IOError, platform::classpath_separator}, diff --git a/theseus/src/launcher/auth.rs b/theseus/src/launcher/auth.rs deleted file mode 100644 index 8f6062649..000000000 --- a/theseus/src/launcher/auth.rs +++ /dev/null @@ -1,84 +0,0 @@ -//! Authentication flow based on Hydra - -use crate::hydra; -use crate::util::fetch::FetchSemaphore; - -use chrono::{prelude::*, Duration}; - -use serde::{Deserialize, Serialize}; - -use crate::api::hydra::stages::{bearer_token, xbl_signin, xsts_token}; - -// Login information -#[derive(Serialize, Deserialize, Clone, Debug)] -pub struct Credentials { - pub id: uuid::Uuid, - pub username: String, - pub access_token: String, - pub refresh_token: String, - pub expires: DateTime, - _ctor_scope: std::marker::PhantomData<()>, -} - -impl Credentials { - pub fn new( - id: uuid::Uuid, - username: String, - access_token: String, - refresh_token: String, - expires: DateTime, - ) -> Self { - Self { - id, - username, - access_token, - refresh_token, - expires, - _ctor_scope: std::marker::PhantomData, - } - } - - pub fn is_expired(&self) -> bool { - self.expires < Utc::now() - } -} - -pub async fn refresh_credentials( - credentials: &mut Credentials, - _semaphore: &FetchSemaphore, -) -> crate::Result<()> { - let oauth = - hydra::refresh::refresh(credentials.refresh_token.clone()).await?; - - let xbl_token = xbl_signin::login_xbl(&oauth.access_token).await?; - - // Get xsts token from xbl token - let xsts_response = xsts_token::fetch_token(&xbl_token.token).await?; - - match xsts_response { - xsts_token::XSTSResponse::Unauthorized(err) => { - return Err(crate::ErrorKind::HydraError(format!( - "Error getting XBox Live token: {}", - err - )) - .as_error()) - } - xsts_token::XSTSResponse::Success { token: xsts_token } => { - let (bearer_token, expires_in) = - bearer_token::fetch_bearer(&xsts_token, &xbl_token.uhs) - .await - .map_err(|err| { - crate::ErrorKind::HydraError(format!( - "Error getting bearer token: {}", - err - )) - })?; - - credentials.access_token = bearer_token; - credentials.refresh_token = oauth.refresh_token; - credentials.expires = Utc::now() + Duration::seconds(expires_in); - } - } - - Ok(()) -} diff --git a/theseus/src/launcher/mod.rs b/theseus/src/launcher/mod.rs index 1d670ba11..9507a52b9 100644 --- a/theseus/src/launcher/mod.rs +++ b/theseus/src/launcher/mod.rs @@ -4,7 +4,7 @@ use crate::event::{LoadingBarId, LoadingBarType}; use crate::jre::{self, JAVA_17_KEY, JAVA_18PLUS_KEY, JAVA_8_KEY}; use crate::launcher::io::IOError; use crate::prelude::JavaVersion; -use crate::state::ProfileInstallStage; +use crate::state::{Credentials, ProfileInstallStage}; use crate::util::io; use crate::{ process, @@ -22,7 +22,6 @@ use uuid::Uuid; mod args; -pub mod auth; pub mod download; // All nones -> disallowed @@ -368,7 +367,7 @@ pub async fn launch_minecraft( wrapper: &Option, memory: &st::MemorySettings, resolution: &st::WindowSize, - credentials: &auth::Credentials, + credentials: &Credentials, post_exit_hook: Option, profile: &Profile, ) -> crate::Result>> { diff --git a/theseus/src/state/auth_task.rs b/theseus/src/state/auth_task.rs deleted file mode 100644 index c8707518a..000000000 --- a/theseus/src/state/auth_task.rs +++ /dev/null @@ -1,86 +0,0 @@ -use crate::{ - hydra::{self, init::DeviceLoginSuccess}, - launcher::auth::Credentials, -}; - -use tokio::task::JoinHandle; - -// Authentication task -// A wrapper over the authentication task that allows it to be called from the frontend -// without caching the task handle in the frontend - -pub struct AuthTask( - #[allow(clippy::type_complexity)] - Option>>, -); - -impl AuthTask { - pub fn new() -> AuthTask { - AuthTask(None) - } - - pub async fn begin_auth() -> crate::Result { - let state = crate::State::get().await?; - // Init task, get url - let login = hydra::init::init().await?; - - // Await completion - let task = tokio::spawn(hydra::complete::wait_finish( - login.device_code.clone(), - )); - - // Flow is going, store in state and return - let mut write = state.auth_flow.write().await; - write.0 = Some(task); - - Ok(login) - } - - pub async fn await_auth_completion() -> crate::Result { - // Gets the task handle from the state, replacing with None - let task = { - let state = crate::State::get().await?; - let mut write = state.auth_flow.write().await; - - write.0.take() - }; - - // Waits for the task to complete, and returns the credentials - let credentials = task - .ok_or(AuthTaskError::TaskMissing)? - .await - .map_err(AuthTaskError::from)??; - - Ok(credentials) - } - - pub async fn cancel() -> crate::Result<()> { - // Gets the task handle from the state, replacing with None - let task = { - let state = crate::State::get().await?; - let mut write = state.auth_flow.write().await; - - write.0.take() - }; - if let Some(task) = task { - // Cancels the task - task.abort(); - } - - Ok(()) - } -} - -impl Default for AuthTask { - fn default() -> Self { - Self::new() - } -} - -#[derive(thiserror::Error, Debug)] -pub enum AuthTaskError { - #[error("Authentication task was aborted or missing")] - TaskMissing, - #[error("Join handle error")] - JoinHandleError(#[from] tokio::task::JoinError), -} diff --git a/theseus/src/state/minecraft_auth.rs b/theseus/src/state/minecraft_auth.rs new file mode 100644 index 000000000..f3224094a --- /dev/null +++ b/theseus/src/state/minecraft_auth.rs @@ -0,0 +1,878 @@ +use crate::data::DirectoryInfo; +use crate::util::fetch::{read_json, write, IoSemaphore, REQWEST_CLIENT}; +use crate::State; +use base64::prelude::{BASE64_STANDARD, BASE64_URL_SAFE_NO_PAD}; +use base64::Engine; +use byteorder::BigEndian; +use chrono::{DateTime, Duration, Utc}; +use p256::ecdsa::signature::Signer; +use p256::ecdsa::{Signature, SigningKey, VerifyingKey}; +use p256::pkcs8::{DecodePrivateKey, EncodePrivateKey, LineEnding}; +use rand::rngs::OsRng; +use rand::Rng; +use reqwest::header::HeaderMap; +use reqwest::Response; +use serde::de::DeserializeOwned; +use serde::{Deserialize, Serialize}; +use serde_json::json; +use std::collections::HashMap; +use std::future::Future; +use uuid::Uuid; + +#[derive(Debug, Clone, Copy)] +pub enum MinecraftAuthStep { + GetDeviceToken, + SisuAuthenicate, + GetOAuthToken, + RefreshOAuthToken, + SisuAuthorize, + XstsAuthorize, + MinecraftToken, + MinecraftEntitlements, + MinecraftProfile, +} + +#[derive(thiserror::Error, Debug)] +pub enum MinecraftAuthenticationError { + #[error("Failed to serialize private key to PEM: {0}")] + PEMSerialize(#[from] p256::pkcs8::Error), + #[error("Failed to serialize body to JSON during step {step:?}: {source}")] + SerializeBody { + step: MinecraftAuthStep, + #[source] + source: serde_json::Error, + }, + #[error( + "Failed to deserialize response to JSON during step {step:?}: {source}" + )] + DeserializeResponse { + step: MinecraftAuthStep, + raw: String, + #[source] + source: serde_json::Error, + }, + #[error("Request failed during step {step:?}: {source}")] + Request { + step: MinecraftAuthStep, + #[source] + source: reqwest::Error, + }, + #[error("Error creating signed request buffer {step:?}: {source}")] + ConstructingSignedRequest { + step: MinecraftAuthStep, + #[source] + source: std::io::Error, + }, + #[error("Error reading user hash")] + NoUserHash, +} + +const AUTH_JSON: &str = "minecraft_auth.json"; + +#[derive(Serialize, Deserialize, Debug)] +pub struct SaveDeviceToken { + pub id: String, + pub private_key: String, + pub x: String, + pub y: String, + pub token: DeviceToken, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct MinecraftLoginFlow { + pub challenge: String, + pub session_id: String, + pub redirect_uri: String, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct MinecraftAuthStore { + pub users: HashMap, + pub token: Option, + pub default_user: Option, +} + +impl MinecraftAuthStore { + #[tracing::instrument] + pub async fn init( + dirs: &DirectoryInfo, + io_semaphore: &IoSemaphore, + ) -> crate::Result { + let auth_path = dirs.caches_meta_dir().await.join(AUTH_JSON); + let store = read_json(&auth_path, io_semaphore).await.ok(); + + if let Some(store) = store { + Ok(store) + } else { + Ok(Self { + users: HashMap::new(), + token: None, + default_user: None, + }) + } + } + + #[tracing::instrument(skip(self))] + pub async fn save(&self) -> crate::Result<()> { + let state = State::get().await?; + let auth_path = + state.directories.caches_meta_dir().await.join(AUTH_JSON); + + write(&auth_path, &serde_json::to_vec(&self)?, &state.io_semaphore) + .await?; + + Ok(()) + } + + #[tracing::instrument(skip(self))] + async fn refresh_and_get_device_token( + &mut self, + ) -> crate::Result<(DeviceTokenKey, DeviceToken)> { + macro_rules! generate_key { + ($self:ident, $generate_key:expr, $device_token:expr, $SaveDeviceToken:path) => {{ + let key = generate_key()?; + let token = device_token(&key).await?; + + self.token = Some(SaveDeviceToken { + id: key.id.clone(), + private_key: key + .key + .to_pkcs8_pem(LineEnding::default()) + .map_err(|err| { + MinecraftAuthenticationError::PEMSerialize(err) + })? + .to_string(), + x: key.x.clone(), + y: key.y.clone(), + token: token.clone(), + }); + self.save().await?; + + (key, token) + }}; + } + + let (key, token) = if let Some(ref token) = self.token { + if token.token.not_after > Utc::now() { + if let Ok(private_key) = + SigningKey::from_pkcs8_pem(&token.private_key) + { + ( + DeviceTokenKey { + id: token.id.clone(), + key: private_key, + x: token.x.clone(), + y: token.y.clone(), + }, + token.token.clone(), + ) + } else { + generate_key!( + self, + generate_key, + device_token, + SaveDeviceToken + ) + } + } else { + generate_key!(self, generate_key, device_token, SaveDeviceToken) + } + } else { + generate_key!(self, generate_key, device_token, SaveDeviceToken) + }; + + Ok((key, token)) + } + + #[tracing::instrument(skip(self))] + pub async fn login_begin(&mut self) -> crate::Result { + let (key, token) = self.refresh_and_get_device_token().await?; + + let challenge = generate_oauth_challenge(); + let (session_id, redirect_uri) = + sisu_authenticate(&token.token, &challenge, &key).await?; + + Ok(MinecraftLoginFlow { + challenge, + session_id, + redirect_uri: redirect_uri.msa_oauth_redirect, + }) + } + + #[tracing::instrument(skip(self))] + pub async fn login_finish( + &mut self, + code: &str, + flow: MinecraftLoginFlow, + ) -> crate::Result { + let (key, token) = self.refresh_and_get_device_token().await?; + + let oauth_token = oauth_token(code, &flow.challenge).await?; + let sisu_authorize = sisu_authorize( + Some(&flow.session_id), + &oauth_token.access_token, + &token.token, + &key, + ) + .await?; + + let xbox_token = + xsts_authorize(sisu_authorize, &token.token, &key).await?; + let minecraft_token = minecraft_token(xbox_token).await?; + + minecraft_entitlements(&minecraft_token.access_token).await?; + + let profile = minecraft_profile(&minecraft_token.access_token).await?; + + let profile_id = profile.id.unwrap_or_default(); + + let credentials = Credentials { + id: profile_id, + username: profile.name, + access_token: minecraft_token.access_token, + refresh_token: oauth_token.refresh_token, + expires: Utc::now() + + Duration::seconds(oauth_token.expires_in as i64), + }; + + self.users.insert(profile_id, credentials.clone()); + + if self.default_user.is_none() { + self.default_user = Some(profile_id); + } + + self.save().await?; + + Ok(credentials) + } + + #[tracing::instrument(skip(self))] + pub async fn get_default_credential( + &mut self, + ) -> crate::Result> { + let credentials = if let Some(default_user) = self.default_user { + if let Some(creds) = self.users.get(&default_user) { + Some(creds) + } else { + self.users.values().next() + } + } else { + self.users.values().next() + }; + + if let Some(creds) = credentials { + if self.default_user != Some(creds.id) { + self.default_user = Some(creds.id); + self.save().await?; + } + + if creds.expires < Utc::now() { + let cred_id = creds.id; + let profile_name = creds.username.clone(); + + let oauth_token = oauth_refresh(&creds.refresh_token).await?; + let (key, token) = self.refresh_and_get_device_token().await?; + + let sisu_authorize = sisu_authorize( + None, + &oauth_token.access_token, + &token.token, + &key, + ) + .await?; + + let xbox_token = + xsts_authorize(sisu_authorize, &token.token, &key).await?; + + let minecraft_token = minecraft_token(xbox_token).await?; + + let val = Credentials { + id: cred_id, + username: profile_name, + access_token: minecraft_token.access_token, + refresh_token: oauth_token.refresh_token, + expires: Utc::now() + + Duration::seconds(oauth_token.expires_in as i64), + }; + + self.users.insert(val.id, val.clone()); + self.save().await?; + + Ok(Some(val)) + } else { + Ok(Some(creds.clone())) + } + } else { + Ok(None) + } + } + + #[tracing::instrument(skip(self))] + pub async fn remove( + &mut self, + id: Uuid, + ) -> crate::Result> { + let val = self.users.remove(&id); + self.save().await?; + Ok(val) + } +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct Credentials { + pub id: Uuid, + pub username: String, + pub access_token: String, + pub refresh_token: String, + pub expires: DateTime, +} + +const MICROSOFT_CLIENT_ID: &str = "00000000402b5328"; +const REDIRECT_URL: &str = "https://login.live.com/oauth20_desktop.srf"; +const REQUESTED_SCOPES: &str = "service::user.auth.xboxlive.com::MBI_SSL"; + +// flow steps +#[derive(Serialize, Deserialize, Clone, Debug)] +#[serde(rename_all = "PascalCase")] +pub struct DeviceToken { + pub issue_instant: DateTime, + pub not_after: DateTime, + pub token: String, + pub display_claims: HashMap, +} + +#[tracing::instrument(skip(key))] +pub async fn device_token( + key: &DeviceTokenKey, +) -> Result { + Ok(send_signed_request( + None, + "https://device.auth.xboxlive.com/device/authenticate", + "/device/authenticate", + json!({ + "Properties": { + "AuthMethod": "ProofOfPossession", + "Id": format!("{{{}}}", key.id), + "DeviceType": "Win32", + "Version": "10.16.0", + "ProofKey": { + "kty": "EC", + "x": key.x, + "y": key.y, + "crv": "P-256", + "alg": "ES256", + "use": "sig" + } + }, + "RelyingParty": "http://auth.xboxlive.com", + "TokenType": "JWT" + + }), + key, + MinecraftAuthStep::GetDeviceToken, + ) + .await? + .1) +} + +#[derive(Deserialize)] +#[serde(rename_all = "PascalCase")] +struct RedirectUri { + pub msa_oauth_redirect: String, +} + +#[tracing::instrument(skip(key))] +async fn sisu_authenticate( + token: &str, + challenge: &str, + key: &DeviceTokenKey, +) -> Result<(String, RedirectUri), MinecraftAuthenticationError> { + let (headers, res) = send_signed_request( + None, + "https://sisu.xboxlive.com/authenticate", + "/authenticate", + json!({ + "AppId": MICROSOFT_CLIENT_ID, + "DeviceToken": token, + "Offers": [ + REQUESTED_SCOPES + ], + "Query": { + "code_challenge": challenge, + "code_challenge_method": "plain", + "state": "", + "prompt": "select_account" + }, + "RedirectUri": REDIRECT_URL, + "Sandbox": "RETAIL", + "TokenType": "code", + }), + key, + MinecraftAuthStep::SisuAuthenicate, + ) + .await?; + + let session_id = headers + .get("X-SessionId") + .and_then(|x| x.to_str().ok()) + .unwrap() + .to_string(); + + Ok((session_id, res)) +} + +#[derive(Deserialize)] +struct OAuthToken { + // pub token_type: String, + pub expires_in: u64, + // pub scope: String, + pub access_token: String, + pub refresh_token: String, + // pub user_id: String, + // pub foci: String, +} + +#[tracing::instrument] +async fn oauth_token( + code: &str, + challenge: &str, +) -> Result { + let mut query = HashMap::new(); + query.insert("client_id", "00000000402b5328"); + query.insert("code", code); + query.insert("code_verifier", challenge); + query.insert("grant_type", "authorization_code"); + query.insert("redirect_uri", "https://login.live.com/oauth20_desktop.srf"); + query.insert("scope", "service::user.auth.xboxlive.com::MBI_SSL"); + + let res = auth_retry(|| { + REQWEST_CLIENT + .post("https://login.live.com/oauth20_token.srf") + .header("Accept", "application/json") + .form(&query) + .send() + }) + .await + .map_err(|source| MinecraftAuthenticationError::Request { + source, + step: MinecraftAuthStep::GetOAuthToken, + })?; + + let text = res.text().await.map_err(|source| { + MinecraftAuthenticationError::Request { + source, + step: MinecraftAuthStep::GetOAuthToken, + } + })?; + + serde_json::from_str(&text).map_err(|source| { + MinecraftAuthenticationError::DeserializeResponse { + source, + raw: text, + step: MinecraftAuthStep::GetOAuthToken, + } + }) +} + +#[tracing::instrument] +async fn oauth_refresh( + refresh_token: &str, +) -> Result { + let mut query = HashMap::new(); + query.insert("client_id", "00000000402b5328"); + query.insert("refresh_token", refresh_token); + query.insert("grant_type", "refresh_token"); + query.insert("redirect_uri", "https://login.live.com/oauth20_desktop.srf"); + query.insert("scope", "service::user.auth.xboxlive.com::MBI_SSL"); + + let res = auth_retry(|| { + REQWEST_CLIENT + .post("https://login.live.com/oauth20_token.srf") + .header("Accept", "application/json") + .form(&query) + .send() + }) + .await + .map_err(|source| MinecraftAuthenticationError::Request { + source, + step: MinecraftAuthStep::RefreshOAuthToken, + })?; + + let text = res.text().await.map_err(|source| { + MinecraftAuthenticationError::Request { + source, + step: MinecraftAuthStep::RefreshOAuthToken, + } + })?; + + serde_json::from_str(&text).map_err(|source| { + MinecraftAuthenticationError::DeserializeResponse { + source, + raw: text, + step: MinecraftAuthStep::RefreshOAuthToken, + } + }) +} + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "PascalCase")] +struct SisuAuthorize { + // pub authorization_token: DeviceToken, + // pub device_token: String, + // pub sandbox: String, + pub title_token: DeviceToken, + pub user_token: DeviceToken, + // pub web_page: String, +} + +#[tracing::instrument(skip(key))] +async fn sisu_authorize( + session_id: Option<&str>, + access_token: &str, + device_token: &str, + key: &DeviceTokenKey, +) -> Result { + Ok(send_signed_request( + None, + "https://sisu.xboxlive.com/authorize", + "/authorize", + json!({ + "AccessToken": format!("t={access_token}"), + "AppId": "00000000402b5328", + "DeviceToken": device_token, + "ProofKey": { + "kty": "EC", + "x": key.x, + "y": key.y, + "crv": "P-256", + "alg": "ES256", + "use": "sig" + }, + "Sandbox": "RETAIL", + "SessionId": session_id, + "SiteName": "user.auth.xboxlive.com", + }), + key, + MinecraftAuthStep::SisuAuthorize, + ) + .await? + .1) +} + +#[tracing::instrument(skip(key))] +async fn xsts_authorize( + authorize: SisuAuthorize, + device_token: &str, + key: &DeviceTokenKey, +) -> Result { + Ok(send_signed_request( + None, + "https://xsts.auth.xboxlive.com/xsts/authorize", + "/xsts/authorize", + json!({ + "RelyingParty": "rp://api.minecraftservices.com/", + "TokenType": "JWT", + "Properties": { + "SandboxId": "RETAIL", + "UserTokens": [authorize.user_token.token], + "DeviceToken": device_token, + "TitleToken": authorize.title_token.token, + }, + }), + key, + MinecraftAuthStep::XstsAuthorize, + ) + .await? + .1) +} + +#[derive(Deserialize)] +struct MinecraftToken { + // pub username: String, + pub access_token: String, + // pub token_type: String, + // pub expires_in: u64, +} + +#[tracing::instrument] +async fn minecraft_token( + token: DeviceToken, +) -> Result { + let uhs = token + .display_claims + .get("xui") + .and_then(|x| x.get(0)) + .and_then(|x| x.get("uhs")) + .and_then(|x| x.as_str().map(String::from)) + .ok_or_else(|| MinecraftAuthenticationError::NoUserHash)?; + + let token = token.token; + + let res = auth_retry(|| { + REQWEST_CLIENT + .post("https://api.minecraftservices.com/launcher/login") + .header("Accept", "application/json") + .json(&json!({ + "platform": "PC_LAUNCHER", + "xtoken": format!("XBL3.0 x={uhs};{token}"), + })) + .send() + }) + .await + .map_err(|source| MinecraftAuthenticationError::Request { + source, + step: MinecraftAuthStep::MinecraftToken, + })?; + + let text = res.text().await.map_err(|source| { + MinecraftAuthenticationError::Request { + source, + step: MinecraftAuthStep::MinecraftToken, + } + })?; + + serde_json::from_str(&text).map_err(|source| { + MinecraftAuthenticationError::DeserializeResponse { + source, + raw: text, + step: MinecraftAuthStep::MinecraftToken, + } + }) +} + +#[derive(Deserialize)] +struct MinecraftProfile { + pub id: Option, + pub name: String, +} + +#[tracing::instrument] +async fn minecraft_profile( + token: &str, +) -> Result { + let res = auth_retry(|| { + REQWEST_CLIENT + .get("https://api.minecraftservices.com/minecraft/profile") + .header("Accept", "application/json") + .bearer_auth(token) + .send() + }) + .await + .map_err(|source| MinecraftAuthenticationError::Request { + source, + step: MinecraftAuthStep::MinecraftProfile, + })?; + + let text = res.text().await.map_err(|source| { + MinecraftAuthenticationError::Request { + source, + step: MinecraftAuthStep::MinecraftProfile, + } + })?; + + serde_json::from_str(&text).map_err(|source| { + MinecraftAuthenticationError::DeserializeResponse { + source, + raw: text, + step: MinecraftAuthStep::MinecraftProfile, + } + }) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct MinecraftEntitlements {} + +#[tracing::instrument] +async fn minecraft_entitlements( + token: &str, +) -> Result { + let res = auth_retry(|| { + REQWEST_CLIENT + .get(format!("https://api.minecraftservices.com/entitlements/license?requestId={}", Uuid::new_v4())) + .header("Accept", "application/json") + .bearer_auth(token) + .send() + }) + .await.map_err(|source| MinecraftAuthenticationError::Request { source, step: MinecraftAuthStep::MinecraftEntitlements })?; + + let text = res.text().await.map_err(|source| { + MinecraftAuthenticationError::Request { + source, + step: MinecraftAuthStep::MinecraftEntitlements, + } + })?; + + serde_json::from_str(&text).map_err(|source| { + MinecraftAuthenticationError::DeserializeResponse { + source, + raw: text, + step: MinecraftAuthStep::MinecraftEntitlements, + } + }) +} + +// auth utils +#[tracing::instrument(skip(reqwest_request))] +async fn auth_retry( + reqwest_request: impl Fn() -> F, +) -> Result +where + F: Future>, +{ + const RETRY_COUNT: usize = 9; // Does command 9 times + const RETRY_WAIT: std::time::Duration = + std::time::Duration::from_millis(250); + + let mut resp = reqwest_request().await?; + for i in 0..RETRY_COUNT { + if resp.status().is_success() { + break; + } + tracing::debug!( + "Request failed with status code {}, retrying...", + resp.status() + ); + if i < RETRY_COUNT - 1 { + tokio::time::sleep(RETRY_WAIT).await; + } + resp = reqwest_request().await?; + } + Ok(resp) +} + +pub struct DeviceTokenKey { + pub id: String, + pub key: SigningKey, + pub x: String, + pub y: String, +} + +#[tracing::instrument] +fn generate_key() -> Result { + let id = Uuid::new_v4().to_string(); + + let signing_key = SigningKey::random(&mut OsRng); + let public_key = VerifyingKey::from(&signing_key); + + let encoded_point = public_key.to_encoded_point(false); + + Ok(DeviceTokenKey { + id, + key: signing_key, + x: BASE64_URL_SAFE_NO_PAD.encode(encoded_point.x().unwrap()), + y: BASE64_URL_SAFE_NO_PAD.encode(encoded_point.y().unwrap()), + }) +} + +#[tracing::instrument(skip(key))] +async fn send_signed_request( + authorization: Option<&str>, + url: &str, + url_path: &str, + raw_body: serde_json::Value, + key: &DeviceTokenKey, + step: MinecraftAuthStep, +) -> Result<(HeaderMap, T), MinecraftAuthenticationError> { + let auth = authorization.map_or(Vec::new(), |v| v.as_bytes().to_vec()); + + let body = serde_json::to_vec(&raw_body).map_err(|source| { + MinecraftAuthenticationError::SerializeBody { source, step } + })?; + let time: u128 = + { ((Utc::now().timestamp() as u128) + 11644473600) * 10000000 }; + + use byteorder::WriteBytesExt; + let mut buffer = Vec::new(); + buffer.write_u32::(1).map_err(|source| { + MinecraftAuthenticationError::ConstructingSignedRequest { source, step } + })?; + buffer.write_u8(0).map_err(|source| { + MinecraftAuthenticationError::ConstructingSignedRequest { source, step } + })?; + buffer + .write_u64::(time as u64) + .map_err(|source| { + MinecraftAuthenticationError::ConstructingSignedRequest { + source, + step, + } + })?; + buffer.write_u8(0).map_err(|source| { + MinecraftAuthenticationError::ConstructingSignedRequest { source, step } + })?; + buffer.extend_from_slice("POST".as_bytes()); + buffer.write_u8(0).map_err(|source| { + MinecraftAuthenticationError::ConstructingSignedRequest { source, step } + })?; + buffer.extend_from_slice(url_path.as_bytes()); + buffer.write_u8(0).map_err(|source| { + MinecraftAuthenticationError::ConstructingSignedRequest { source, step } + })?; + buffer.extend_from_slice(&auth); + buffer.write_u8(0).map_err(|source| { + MinecraftAuthenticationError::ConstructingSignedRequest { source, step } + })?; + buffer.extend_from_slice(&body); + buffer.write_u8(0).map_err(|source| { + MinecraftAuthenticationError::ConstructingSignedRequest { source, step } + })?; + + let ecdsa_sig: Signature = key.key.sign(&buffer); + + let mut sig_buffer = Vec::new(); + sig_buffer.write_i32::(1).map_err(|source| { + MinecraftAuthenticationError::ConstructingSignedRequest { source, step } + })?; + sig_buffer + .write_u64::(time as u64) + .map_err(|source| { + MinecraftAuthenticationError::ConstructingSignedRequest { + source, + step, + } + })?; + sig_buffer.extend_from_slice(&ecdsa_sig.r().to_bytes()); + sig_buffer.extend_from_slice(&ecdsa_sig.s().to_bytes()); + + let signature = BASE64_STANDARD.encode(&sig_buffer); + + let res = auth_retry(|| { + let mut request = REQWEST_CLIENT + .post(url) + .header("Content-Type", "application/json") + .header("Accept", "application/json") + .header("x-xbl-contract-version", "1") + .header("signature", &signature); + + if let Some(auth) = authorization { + request = request.header("Authorization", auth); + } + + request.body(body.clone()).send() + }) + .await + .map_err(|source| MinecraftAuthenticationError::Request { source, step })?; + + let headers = res.headers().clone(); + let res = res.text().await.map_err(|source| { + MinecraftAuthenticationError::Request { source, step } + })?; + + let body = serde_json::from_str(&res).map_err(|source| { + MinecraftAuthenticationError::DeserializeResponse { + source, + raw: res, + step, + } + })?; + Ok((headers, body)) +} + +#[tracing::instrument] +fn generate_oauth_challenge() -> String { + let mut rng = rand::thread_rng(); + + let bytes: Vec = (0..32).map(|_| rng.gen()).collect(); + bytes.iter().map(|byte| format!("{:02x}", byte)).collect() +} diff --git a/theseus/src/state/mod.rs b/theseus/src/state/mod.rs index 26b3fd619..a0e510e0c 100644 --- a/theseus/src/state/mod.rs +++ b/theseus/src/state/mod.rs @@ -5,7 +5,6 @@ use std::path::PathBuf; use crate::event::LoadingBarType; use crate::loading_join; -use crate::state::users::Users; use crate::util::fetch::{self, FetchSemaphore, IoSemaphore}; use notify::RecommendedWatcher; use notify_debouncer_mini::{new_debouncer, DebounceEventResult, Debouncer}; @@ -32,14 +31,9 @@ pub use self::settings::*; mod projects; pub use self::projects::*; -mod users; - mod children; pub use self::children::*; -mod auth_task; -pub use self::auth_task::*; - mod tags; pub use self::tags::*; @@ -52,6 +46,9 @@ pub use self::safe_processes::*; mod discord; pub use self::discord::*; +mod minecraft_auth; +pub use self::minecraft_auth::*; + mod mr_auth; pub use self::mr_auth::*; @@ -87,9 +84,7 @@ pub struct State { /// Launcher processes that should be safely exited on shutdown pub(crate) safety_processes: RwLock, /// Launcher user account info - pub(crate) users: RwLock, - /// Authentication flow - pub auth_flow: RwLock, + pub(crate) users: RwLock, /// Modrinth Credentials Store pub credentials: RwLock, /// Modrinth auth flow @@ -172,7 +167,7 @@ impl State { &fetch_semaphore, &CredentialsStore(None), ); - let users_fut = Users::init(&directories, &io_semaphore); + let users_fut = MinecraftAuthStore::init(&directories, &io_semaphore); let creds_fut = CredentialsStore::init(&directories, &io_semaphore); // Launcher data let (metadata, profiles, tags, users, creds) = loading_join! { @@ -184,7 +179,6 @@ impl State { creds_fut, }?; - let auth_flow = AuthTask::new(); let safety_processes = SafeProcesses::new(); let discord_rpc = DiscordGuard::init(is_offline).await?; @@ -217,7 +211,6 @@ impl State { profiles: RwLock::new(profiles), users: RwLock::new(users), children: RwLock::new(children), - auth_flow: RwLock::new(auth_flow), credentials: RwLock::new(creds), tags: RwLock::new(tags), discord_rpc, @@ -253,9 +246,8 @@ impl State { let res4 = Profiles::update_projects(); let res5 = Settings::update_java(); let res6 = CredentialsStore::update_creds(); - let res7 = Settings::update_default_user(); - let _ = join!(res1, res2, res3, res4, res5, res6, res7); + let _ = join!(res1, res2, res3, res4, res5, res6); } } }); diff --git a/theseus/src/state/settings.rs b/theseus/src/state/settings.rs index dda34bc57..6d04e4bac 100644 --- a/theseus/src/state/settings.rs +++ b/theseus/src/state/settings.rs @@ -24,7 +24,6 @@ pub struct Settings { pub custom_java_args: Vec, pub custom_env_args: Vec<(String, String)>, pub java_globals: JavaGlobals, - pub default_user: Option, pub hooks: Hooks, pub max_concurrent_downloads: usize, pub max_concurrent_writes: usize, @@ -93,7 +92,6 @@ impl Settings { custom_java_args: Vec::new(), custom_env_args: Vec::new(), java_globals: JavaGlobals::new(), - default_user: None, hooks: Hooks::default(), max_concurrent_downloads: 10, max_concurrent_writes: 10, @@ -152,32 +150,6 @@ impl Settings { }; } - #[tracing::instrument] - #[theseus_macros::debug_pin] - pub async fn update_default_user() { - let res = async { - let state = State::get().await?; - let settings_read = state.settings.read().await; - - if settings_read.default_user.is_none() { - drop(settings_read); - let users = state.users.read().await; - let user = users.0.iter().next().map(|(id, _)| *id); - state.settings.write().await.default_user = user; - } - - Ok::<(), crate::Error>(()) - } - .await; - - match res { - Ok(()) => {} - Err(err) => { - tracing::warn!("Unable to update default user: {err}") - } - }; - } - #[tracing::instrument(skip(self))] pub async fn sync(&self, to: &Path) -> crate::Result<()> { fs::write(to, serde_json::to_vec(self)?) diff --git a/theseus/src/state/users.rs b/theseus/src/state/users.rs deleted file mode 100644 index d88b4d04c..000000000 --- a/theseus/src/state/users.rs +++ /dev/null @@ -1,70 +0,0 @@ -//! User login info -use crate::auth::Credentials; -use crate::data::DirectoryInfo; -use crate::util::fetch::{read_json, write, IoSemaphore}; -use crate::State; -use std::collections::HashMap; -use uuid::Uuid; - -const USERS_JSON: &str = "users.json"; - -/// The set of users stored in the launcher -#[derive(Clone)] -pub(crate) struct Users(pub(crate) HashMap); - -impl Users { - pub async fn init( - dirs: &DirectoryInfo, - io_semaphore: &IoSemaphore, - ) -> crate::Result { - let users_path = dirs.caches_meta_dir().await.join(USERS_JSON); - let users = read_json(&users_path, io_semaphore).await.ok(); - - if let Some(users) = users { - Ok(Self(users)) - } else { - Ok(Self(HashMap::new())) - } - } - - pub async fn save(&self) -> crate::Result<()> { - let state = State::get().await?; - let users_path = - state.directories.caches_meta_dir().await.join(USERS_JSON); - write( - &users_path, - &serde_json::to_vec(&self.0)?, - &state.io_semaphore, - ) - .await?; - - Ok(()) - } - - #[tracing::instrument(skip_all)] - pub async fn insert( - &mut self, - credentials: &Credentials, - ) -> crate::Result<&Self> { - self.0.insert(credentials.id, credentials.clone()); - self.save().await?; - Ok(self) - } - - #[tracing::instrument(skip(self))] - pub fn contains(&self, id: Uuid) -> bool { - self.0.contains_key(&id) - } - - #[tracing::instrument(skip(self))] - pub fn get(&self, id: Uuid) -> Option { - self.0.get(&id).cloned() - } - - #[tracing::instrument(skip(self))] - pub async fn remove(&mut self, id: Uuid) -> crate::Result<&Self> { - self.0.remove(&id); - self.save().await?; - Ok(self) - } -} diff --git a/theseus_cli/Cargo.toml b/theseus_cli/Cargo.toml deleted file mode 100644 index 806007e9b..000000000 --- a/theseus_cli/Cargo.toml +++ /dev/null @@ -1,34 +0,0 @@ -[package] -name = "theseus_cli" -version = "0.6.3" -authors = ["Jai A "] -edition = "2018" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -theseus = { path = "../theseus", features = ["cli"] } -daedalus = {version = "0.1.15", features = ["bincode"]} -tokio = { version = "1", features = ["full"] } -tokio-stream = { version = "0.1", features = ["fs"] } -futures = "0.3" -argh = "0.1" -paris = { version = "1.5", features = ["macros", "no_logger"] } -dialoguer = "0.11.0" -tabled = "0.15.0" -dirs = "5.0.1" -uuid = {version = "1.1", features = ["v4", "serde"]} -url = "2.2" - -color-eyre = "0.6" -eyre = "0.6" -tracing = "0.1" -tracing-error = "0.2" -tracing-futures = "0.2" -tracing-subscriber = {version = "0.3", features = ["env-filter"]} -dunce = "1.0.3" - -webbrowser = "0.8.13" - -[target.'cfg(windows)'.dependencies] -winreg = "0.52.0" diff --git a/theseus_cli/src/main.rs b/theseus_cli/src/main.rs deleted file mode 100644 index ff868b4b4..000000000 --- a/theseus_cli/src/main.rs +++ /dev/null @@ -1,52 +0,0 @@ -use eyre::Result; -use futures::TryFutureExt; -use paris::*; -use tracing_error::ErrorLayer; -use tracing_futures::WithSubscriber; -use tracing_subscriber::prelude::*; -use tracing_subscriber::{fmt, EnvFilter}; - -#[macro_use] -mod util; - -mod subcommands; - -#[derive(argh::FromArgs, Debug)] -/// The official Modrinth CLI -pub struct Args { - #[argh(subcommand)] - pub subcommand: subcommands::Subcommand, -} - -#[tracing::instrument] -fn main() -> Result<()> { - let args = argh::from_env::(); - - color_eyre::install()?; - let filter = EnvFilter::try_from_default_env() - .or_else(|_| EnvFilter::try_new("info"))?; - - let format = fmt::layer() - .without_time() - .with_writer(std::io::stderr) - .with_target(false) - .compact(); - - tracing_subscriber::registry() - .with(format) - .with(filter) - .with(ErrorLayer::default()) - .init(); - - tokio::runtime::Builder::new_multi_thread() - .enable_all() - .build()? - .block_on( - async move { - args.dispatch() - .inspect_err(|_| error!("An error has occurred!\n")) - .await - } - .with_current_subscriber(), - ) -} diff --git a/theseus_cli/src/subcommands/mod.rs b/theseus_cli/src/subcommands/mod.rs deleted file mode 100644 index 04cc48080..000000000 --- a/theseus_cli/src/subcommands/mod.rs +++ /dev/null @@ -1,20 +0,0 @@ -use eyre::Result; - -mod profile; -mod user; - -#[derive(argh::FromArgs, Debug)] -#[argh(subcommand)] -pub enum Subcommand { - Profile(profile::ProfileCommand), - User(user::UserCommand), -} - -impl crate::Args { - pub async fn dispatch(&self) -> Result<()> { - dispatch!(self.subcommand, (self) => { - Subcommand::Profile, - Subcommand::User - }) - } -} diff --git a/theseus_cli/src/subcommands/profile.rs b/theseus_cli/src/subcommands/profile.rs deleted file mode 100644 index ec8c0f9a2..000000000 --- a/theseus_cli/src/subcommands/profile.rs +++ /dev/null @@ -1,372 +0,0 @@ -//! Profile management subcommand -use crate::util::table_path_display; -use crate::util::{confirm_async, prompt_async, select_async, table}; -use daedalus::modded::LoaderVersion; -use dunce::canonicalize; -use eyre::{ensure, Result}; -use futures::prelude::*; -use paris::*; -use std::path::{Path, PathBuf}; -use tabled::settings::object::Columns; -use tabled::settings::{Modify, Width}; -use tabled::Tabled; -use theseus::prelude::*; -use theseus::profile::create::profile_create; -use tokio::fs; -use tokio_stream::wrappers::ReadDirStream; - -#[derive(argh::FromArgs, Debug)] -#[argh(subcommand, name = "profile")] -/// manage Minecraft instances -pub struct ProfileCommand { - #[argh(subcommand)] - action: ProfileSubcommand, -} - -#[derive(argh::FromArgs, Debug)] -#[argh(subcommand)] -pub enum ProfileSubcommand { - Init(ProfileInit), - List(ProfileList), - Remove(ProfileRemove), - Run(ProfileRun), -} - -#[derive(argh::FromArgs, Debug)] -#[argh(subcommand, name = "init")] -/// create a new profile and manage it with Theseus -pub struct ProfileInit { - #[argh(positional, default = "std::env::current_dir().unwrap()")] - /// the path of the newly created profile - path: PathBuf, - - #[argh(option)] - /// the name of the profile - name: Option, - - #[argh(option)] - /// the game version of the profile - game_version: Option, - - #[argh(option, from_str_fn(modloader_from_str))] - /// the modloader to use - modloader: Option, - - #[argh(option)] - /// the modloader version to use, set to "latest", "stable", or the ID of your chosen loader - loader_version: Option, -} - -impl ProfileInit { - pub async fn run( - &self, - _args: &crate::Args, - _largs: &ProfileCommand, - ) -> Result<()> { - // TODO: validate inputs from args early - let state = State::get().await?; - let metadata = state.metadata.read().await; - - if self.path.exists() { - ensure!( - self.path.is_dir(), - "Attempted to create profile in something other than a folder!" - ); - ensure!( - !self.path.join("profile.json").exists(), - "Profile already exists! Perhaps you want `profile add` instead?" - ); - if ReadDirStream::new(fs::read_dir(&self.path).await?) - .next() - .await - .is_some() - { - warn!("You are trying to create a profile in a non-empty directory. If this is an instance from another launcher, please be sure to properly fill the profile.json fields!"); - if !confirm_async( - String::from("Do you wish to continue"), - false, - ) - .await? - { - eyre::bail!("Aborted!"); - } - } - } else { - fs::create_dir_all(&self.path).await?; - } - info!( - "Creating profile at path {}", - &canonicalize(&self.path)?.display() - ); - - // TODO: abstract default prompting - let name = match &self.name { - Some(name) => name.clone(), - None => { - let default = self.path.file_name().unwrap().to_string_lossy(); - - prompt_async( - String::from("Instance name"), - Some(default.into_owned()), - ) - .await? - } - }; - - let game_version = match &self.game_version { - Some(version) => version.clone(), - None => { - let default = &metadata.minecraft.latest.release; - - prompt_async( - String::from("Game version"), - Some(default.clone()), - ) - .await? - } - }; - - let loader = match &self.modloader { - Some(loader) => *loader, - None => { - let choice = select_async( - "Modloader".to_owned(), - &["vanilla", "fabric", "forge"], - ) - .await?; - - match choice { - 0 => ModLoader::Vanilla, - 1 => ModLoader::Fabric, - 2 => ModLoader::Forge, - _ => eyre::bail!( - "Invalid modloader ID: {choice}. This is a bug in the launcher!" - ), - } - } - }; - - let loader = if loader != ModLoader::Vanilla { - let version = match &self.loader_version { - Some(version) => String::from(version), - None => prompt_async( - String::from( - "Modloader version (latest, stable, or a version ID)", - ), - Some(String::from("latest")), - ) - .await?, - }; - - let filter = |it: &LoaderVersion| match version.as_str() { - "latest" => true, - "stable" => it.stable, - id => it.id == *id, - }; - - let loader_data = match loader { - ModLoader::Forge => &metadata.forge, - ModLoader::Fabric => &metadata.fabric, - _ => eyre::bail!("Could not get manifest for loader {loader}. This is a bug in the CLI!"), - }; - - let loaders = &loader_data.game_versions - .iter() - .find(|it| it.id == game_version) - .ok_or_else(|| eyre::eyre!("Modloader {loader} unsupported for Minecraft version {game_version}"))? - .loaders; - - let loader_version = loaders - .iter() - .find(|&x| filter(x)) - .cloned() - .ok_or_else(|| { - eyre::eyre!("Invalid version {version} for modloader {loader}") - })?; - - Some((loader_version, loader)) - } else { - None - }; - - profile_create( - name, - game_version, - loader.clone().map(|x| x.1).unwrap_or(ModLoader::Vanilla), - loader.map(|x| x.0.id), - None, - None, - None, - None, - None, - ) - .await?; - - success!( - "Successfully created instance, it is now available to use with Theseus!" - ); - Ok(()) - } -} - -#[derive(argh::FromArgs, Debug)] -/// list all managed profiles -#[argh(subcommand, name = "list")] -pub struct ProfileList {} - -#[derive(Tabled)] -struct ProfileRow<'a> { - name: &'a str, - #[tabled(display_with = "table_path_display")] - path: &'a Path, - #[tabled(rename = "game version")] - game_version: &'a str, - loader: &'a ModLoader, - #[tabled(rename = "loader version")] - loader_version: &'a str, -} - -impl<'a> From<&'a Profile> for ProfileRow<'a> { - fn from(it: &'a Profile) -> Self { - Self { - name: &it.metadata.name, - path: Path::new(&it.metadata.name), - game_version: &it.metadata.game_version, - loader: &it.metadata.loader, - loader_version: it - .metadata - .loader_version - .as_ref() - .map_or("", |it| &it.id), - } - } -} - -impl<'a> From<&'a Path> for ProfileRow<'a> { - fn from(it: &'a Path) -> Self { - Self { - name: "?", - path: it, - game_version: "?", - loader: &ModLoader::Vanilla, - loader_version: "?", - } - } -} - -impl ProfileList { - pub async fn run( - &self, - _args: &crate::Args, - _largs: &ProfileCommand, - ) -> Result<()> { - let profiles = profile::list(None).await?; - let rows = profiles.values().map(ProfileRow::from); - - let mut table = table(rows); - table.with(Modify::new(Columns::new(1..=1)).with(Width::wrap(40))); - - println!("{table}"); - - Ok(()) - } -} - -#[derive(argh::FromArgs, Debug)] -/// unmanage a profile -#[argh(subcommand, name = "remove")] -pub struct ProfileRemove { - #[argh(positional, default = "std::env::current_dir().unwrap()")] - /// the profile to get rid of - profile: PathBuf, -} - -impl ProfileRemove { - pub async fn run( - &self, - _args: &crate::Args, - _largs: &ProfileCommand, - ) -> Result<()> { - let profile = - ProfilePathId::from_fs_path(canonicalize(&self.profile)?).await?; - info!("Removing profile {} from Theseus", self.profile.display()); - - if confirm_async(String::from("Do you wish to continue"), true).await? { - profile::remove(&profile).await?; - State::sync().await?; - success!("Profile removed!"); - } else { - error!("Aborted!"); - } - - Ok(()) - } -} - -#[derive(argh::FromArgs, Debug)] -/// run a profile -#[argh(subcommand, name = "run")] -pub struct ProfileRun { - #[argh(positional, default = "std::env::current_dir().unwrap()")] - /// the profile to run - profile: PathBuf, - - #[argh(option)] - /// the user to authenticate with - user: Option, -} - -impl ProfileRun { - pub async fn run( - &self, - _args: &crate::Args, - _largs: &ProfileCommand, - ) -> Result<()> { - info!("Starting profile at path {}...", self.profile.display()); - let path = canonicalize(&self.profile)?; - - let id = future::ready(self.user.ok_or(())) - .or_else(|_| async move { - let state = State::get().await?; - let settings = state.settings.read().await; - - settings.default_user - .ok_or(eyre::eyre!( - "Could not find any users, please add one using the `user add` command." - )) - }) - .await?; - let credentials = auth::refresh(id).await?; - - let profile_path_id = ProfilePathId::from_fs_path(path).await?; - let proc_lock = - profile::run_credentials(&profile_path_id, &credentials).await?; - let mut proc = proc_lock.write().await; - process::wait_for(&mut proc).await?; - - success!("Process exited successfully!"); - Ok(()) - } -} - -impl ProfileCommand { - pub async fn run(&self, args: &crate::Args) -> Result<()> { - dispatch!(&self.action, (args, self) => { - ProfileSubcommand::Init, - ProfileSubcommand::List, - ProfileSubcommand::Remove, - ProfileSubcommand::Run - }) - } -} - -fn modloader_from_str(it: &str) -> core::result::Result { - match it { - "vanilla" => Ok(ModLoader::Vanilla), - "forge" => Ok(ModLoader::Forge), - "fabric" => Ok(ModLoader::Fabric), - "quilt" => Ok(ModLoader::Quilt), - "neoforge" => Ok(ModLoader::NeoForge), - _ => Err(String::from("Invalid modloader: {it}")), - } -} diff --git a/theseus_cli/src/subcommands/user.rs b/theseus_cli/src/subcommands/user.rs deleted file mode 100644 index 0c0a5dbf5..000000000 --- a/theseus_cli/src/subcommands/user.rs +++ /dev/null @@ -1,181 +0,0 @@ -//! User management subcommand -use crate::util::{confirm_async, table}; -use eyre::Result; -use paris::*; -use tabled::Tabled; -use theseus::prelude::*; - -#[derive(argh::FromArgs, Debug)] -#[argh(subcommand, name = "user")] -/// manage Minecraft accounts -pub struct UserCommand { - #[argh(subcommand)] - action: UserSubcommand, -} - -#[derive(argh::FromArgs, Debug)] -#[argh(subcommand)] -pub enum UserSubcommand { - Add(UserAdd), - List(UserList), - Remove(UserRemove), - SetDefault(UserDefault), -} - -#[derive(argh::FromArgs, Debug)] -/// add a new user to Theseus -#[argh(subcommand, name = "add")] -pub struct UserAdd { - #[argh(option)] - /// the browser to authenticate using - browser: Option, -} - -impl UserAdd { - pub async fn run( - &self, - _args: &crate::Args, - _largs: &UserCommand, - ) -> Result<()> { - info!("Adding new user account to Theseus"); - info!("A browser window will now open, follow the login flow there."); - - let login = auth::authenticate_begin_flow().await?; - let flow = tokio::spawn(auth::authenticate_await_complete_flow()); - - info!("Opening browser window at {}", login.verification_uri); - info!("Your code is {}", login.user_code); - - match self.browser { - Some(browser) => webbrowser::open_browser( - browser, - login.verification_uri.as_str(), - ), - None => webbrowser::open(login.verification_uri.as_str()), - }?; - - let credentials = flow.await??; - State::sync().await?; - success!("Logged in user {}.", credentials.username); - Ok(()) - } -} - -#[derive(argh::FromArgs, Debug)] -/// list all known users -#[argh(subcommand, name = "list")] -pub struct UserList {} - -#[derive(Tabled)] -struct UserRow<'a> { - username: &'a str, - id: uuid::Uuid, - default: bool, -} - -impl<'a> UserRow<'a> { - pub fn from( - credentials: &'a Credentials, - default: Option, - ) -> Self { - Self { - username: &credentials.username, - id: credentials.id, - default: Some(credentials.id) == default, - } - } -} - -impl UserList { - pub async fn run( - &self, - _args: &crate::Args, - _largs: &UserCommand, - ) -> Result<()> { - let state = State::get().await?; - let default = state.settings.read().await.default_user; - - let users = auth::users().await?; - let rows = users.iter().map(|user| UserRow::from(user, default)); - - let table = table(rows); - println!("{table}"); - - Ok(()) - } -} - -#[derive(argh::FromArgs, Debug)] -/// remove a user -#[argh(subcommand, name = "remove")] -pub struct UserRemove { - /// the user to remove - #[argh(positional)] - user: uuid::Uuid, -} - -impl UserRemove { - pub async fn run( - &self, - _args: &crate::Args, - _largs: &UserCommand, - ) -> Result<()> { - info!("Removing user {}", self.user.as_hyphenated()); - - if confirm_async(String::from("Do you wish to continue"), true).await? { - if !auth::has_user(self.user).await? { - warn!("Profile was not managed by Theseus!"); - } else { - auth::remove_user(self.user).await?; - State::sync().await?; - success!("User removed!"); - } - } else { - error!("Aborted!"); - } - - Ok(()) - } -} - -#[derive(argh::FromArgs, Debug)] -/// set the default user -#[argh(subcommand, name = "set-default")] -pub struct UserDefault { - /// the user to set as default - #[argh(positional)] - user: uuid::Uuid, -} - -impl UserDefault { - pub async fn run( - &self, - _args: &crate::Args, - _largs: &UserCommand, - ) -> Result<()> { - info!("Setting user {} as default", self.user.as_hyphenated()); - - let state = State::get().await?; - let mut settings = state.settings.write().await; - - if settings.default_user == Some(self.user) { - warn!("User is already the default!"); - } else { - settings.default_user = Some(self.user); - success!("User set as default!"); - } - - Ok(()) - } -} - -impl UserCommand { - pub async fn run(&self, args: &crate::Args) -> Result<()> { - dispatch!(&self.action, (args, self) => { - UserSubcommand::Add, - UserSubcommand::List, - UserSubcommand::Remove, - UserSubcommand::SetDefault - }) - } -} diff --git a/theseus_cli/src/util.rs b/theseus_cli/src/util.rs deleted file mode 100644 index 114c10239..000000000 --- a/theseus_cli/src/util.rs +++ /dev/null @@ -1,95 +0,0 @@ -use dialoguer::{Confirm, Input, Select}; -use eyre::Result; -use std::{borrow::Cow, path::Path}; -use tabled::settings::Style; -use tabled::{Table, Tabled}; - -// TODO: make primarily async to avoid copies - -// Prompting helpers -pub fn prompt(prompt: &str, default: Option) -> Result { - let prompt = match default.as_deref() { - Some("") => Cow::Owned(format!("{prompt} (optional)")), - Some(default) => Cow::Owned(format!("{prompt} (default: {default})")), - None => Cow::Borrowed(prompt), - }; - print_prompt(&prompt); - - let mut input = Input::::new().with_prompt("").show_default(false); - - if let Some(default) = default { - input = input.default(default); - } - - Ok(input.interact_text()?.trim().to_owned()) -} - -pub async fn prompt_async( - text: String, - default: Option, -) -> Result { - tokio::task::spawn_blocking(move || prompt(&text, default)).await? -} - -// Selection helpers -pub fn select(prompt: &str, choices: &[&str]) -> Result { - print_prompt(prompt); - - let res = Select::new().items(choices).default(0).interact()?; - eprintln!("> {}", choices[res]); - Ok(res) -} - -pub async fn select_async( - prompt: String, - choices: &'static [&'static str], -) -> Result { - tokio::task::spawn_blocking(move || select(&prompt, choices)).await? -} - -// Confirmation helpers -pub fn confirm(prompt: &str, default: bool) -> Result { - print_prompt(prompt); - Ok(Confirm::new().default(default).interact()?) -} - -pub async fn confirm_async(prompt: String, default: bool) -> Result { - tokio::task::spawn_blocking(move || confirm(&prompt, default)).await? -} - -// Table helpers -pub fn table(rows: impl IntoIterator) -> Table { - Table::new(rows).with(Style::psql()).clone() -} - -pub fn table_path_display(path: &Path) -> String { - let mut res = path.display().to_string(); - - if let Some(home_dir) = dirs::home_dir() { - res = res.replace(&home_dir.display().to_string(), "~"); - } - - res -} - -// Dispatch macros -macro_rules! dispatch { - ($on:expr, $args:tt => {$($option:path),+}) => { - match $on { - $($option (ref cmd) => dispatch!(@apply cmd => $args)),+ - } - }; - - (@apply $cmd:expr => ($($args:expr),*)) => {{ - use tracing_futures::WithSubscriber; - $cmd.run($($args),*).with_current_subscriber().await - }}; -} - -// Internal helpers -fn print_prompt(prompt: &str) { - println!( - "{}", - paris::formatter::colorize_string(format!("? {prompt}:")) - ); -} diff --git a/theseus_gui/package.json b/theseus_gui/package.json index 540a8694d..953329614 100644 --- a/theseus_gui/package.json +++ b/theseus_gui/package.json @@ -20,7 +20,6 @@ "ofetch": "^1.3.4", "omorphia": "^0.4.41", "pinia": "^2.1.7", - "qrcode.vue": "^3.4.1", "tauri-plugin-window-state-api": "github:tauri-apps/tauri-plugin-window-state#v1", "vite-svg-loader": "^5.1.0", "vue": "^3.4.21", diff --git a/theseus_gui/pnpm-lock.yaml b/theseus_gui/pnpm-lock.yaml index a0f1e07e4..4a980b88a 100644 --- a/theseus_gui/pnpm-lock.yaml +++ b/theseus_gui/pnpm-lock.yaml @@ -26,9 +26,6 @@ dependencies: pinia: specifier: ^2.1.7 version: 2.1.7(vue@3.4.21) - qrcode.vue: - specifier: ^3.4.1 - version: 3.4.1(vue@3.4.21) tauri-plugin-window-state-api: specifier: github:tauri-apps/tauri-plugin-window-state#v1 version: github.com/tauri-apps/tauri-plugin-window-state/002cf15f6a1e4969a678a4ade680cd60477a8a53 @@ -87,12 +84,12 @@ packages: engines: {node: '>=0.10.0'} dev: true - /@babel/helper-string-parser@7.24.1: - resolution: {integrity: sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==} + /@babel/helper-string-parser@7.21.5: + resolution: {integrity: sha512-5pTUx3hAJaZIdW99sJ6ZUUgWq/Y+Hja7TowEnLNMm1VivRgZQL3vpBY3qUACVsvw+yQU6+YgfBVmcbLaZtrA1w==} engines: {node: '>=6.9.0'} - /@babel/helper-validator-identifier@7.22.20: - resolution: {integrity: sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==} + /@babel/helper-validator-identifier@7.19.1: + resolution: {integrity: sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==} engines: {node: '>=6.9.0'} /@babel/parser@7.24.4: @@ -100,14 +97,14 @@ packages: engines: {node: '>=6.0.0'} hasBin: true dependencies: - '@babel/types': 7.24.0 + '@babel/types': 7.22.4 - /@babel/types@7.24.0: - resolution: {integrity: sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==} + /@babel/types@7.22.4: + resolution: {integrity: sha512-Tx9x3UBHTTsMSW85WB2kphxYQVvrZ/t1FxD88IpSgIjiUJlCm9z+xWIDwyo1vffTwSqteqyznB8ZE9vYYk16zA==} engines: {node: '>=6.9.0'} dependencies: - '@babel/helper-string-parser': 7.24.1 - '@babel/helper-validator-identifier': 7.22.20 + '@babel/helper-string-parser': 7.21.5 + '@babel/helper-validator-identifier': 7.19.1 to-fast-properties: 2.0.0 /@esbuild/aix-ppc64@0.20.2: @@ -340,7 +337,7 @@ packages: debug: 4.3.4 espree: 9.6.1 globals: 13.24.0 - ignore: 5.3.1 + ignore: 5.2.4 import-fresh: 3.3.0 js-yaml: 4.1.0 minimatch: 3.1.2 @@ -354,12 +351,22 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true + /@floating-ui/core@0.3.1: + resolution: {integrity: sha512-ensKY7Ub59u16qsVIFEo2hwTCqZ/r9oZZFh51ivcLGHfUwTn8l1Xzng8RJUe91H/UP8PeqeBronAGx0qmzwk2g==} + dev: false + /@floating-ui/core@1.6.0: resolution: {integrity: sha512-PcF++MykgmTj3CIyOQbKA/hDzOAiqI3mhuoN44WRCopIs1sgoDoU4oty4Jtqaj/y3oDU6fnVSm4QG0a3t5i0+g==} dependencies: '@floating-ui/utils': 0.2.1 dev: false + /@floating-ui/dom@0.1.10: + resolution: {integrity: sha512-4kAVoogvQm2N0XE0G6APQJuCNuErjOfPW8Ux7DFxh8+AfugWflwVJ5LDlHOwrwut7z/30NUvdtHzQ3zSip4EzQ==} + dependencies: + '@floating-ui/core': 0.3.1 + dev: false + /@floating-ui/dom@1.1.1: resolution: {integrity: sha512-TpIO93+DIujg3g7SykEAGZMDtbJRrmnYRCNYSjJlvIbGhBjRSNTLVbNeDQBrzy9qDgUbiWdc7KA0uZHZ2tJmiw==} dependencies: @@ -415,7 +422,7 @@ packages: engines: {node: '>= 8'} dependencies: '@nodelib/fs.scandir': 2.1.5 - fastq: 1.17.1 + fastq: 1.15.0 dev: true /@rollup/plugin-alias@5.1.0: @@ -438,120 +445,120 @@ packages: picomatch: 2.3.1 dev: true - /@rollup/rollup-android-arm-eabi@4.14.0: - resolution: {integrity: sha512-jwXtxYbRt1V+CdQSy6Z+uZti7JF5irRKF8hlKfEnF/xJpcNGuuiZMBvuoYM+x9sr9iWGnzrlM0+9hvQ1kgkf1w==} + /@rollup/rollup-android-arm-eabi@4.14.1: + resolution: {integrity: sha512-fH8/o8nSUek8ceQnT7K4EQbSiV7jgkHq81m9lWZFIXjJ7lJzpWXbQFpT/Zh6OZYnpFykvzC3fbEvEAFZu03dPA==} cpu: [arm] os: [android] requiresBuild: true dev: true optional: true - /@rollup/rollup-android-arm64@4.14.0: - resolution: {integrity: sha512-fI9nduZhCccjzlsA/OuAwtFGWocxA4gqXGTLvOyiF8d+8o0fZUeSztixkYjcGq1fGZY3Tkq4yRvHPFxU+jdZ9Q==} + /@rollup/rollup-android-arm64@4.14.1: + resolution: {integrity: sha512-Y/9OHLjzkunF+KGEoJr3heiD5X9OLa8sbT1lm0NYeKyaM3oMhhQFvPB0bNZYJwlq93j8Z6wSxh9+cyKQaxS7PQ==} cpu: [arm64] os: [android] requiresBuild: true dev: true optional: true - /@rollup/rollup-darwin-arm64@4.14.0: - resolution: {integrity: sha512-BcnSPRM76/cD2gQC+rQNGBN6GStBs2pl/FpweW8JYuz5J/IEa0Fr4AtrPv766DB/6b2MZ/AfSIOSGw3nEIP8SA==} + /@rollup/rollup-darwin-arm64@4.14.1: + resolution: {integrity: sha512-+kecg3FY84WadgcuSVm6llrABOdQAEbNdnpi5X3UwWiFVhZIZvKgGrF7kmLguvxHNQy+UuRV66cLVl3S+Rkt+Q==} cpu: [arm64] os: [darwin] requiresBuild: true dev: true optional: true - /@rollup/rollup-darwin-x64@4.14.0: - resolution: {integrity: sha512-LDyFB9GRolGN7XI6955aFeI3wCdCUszFWumWU0deHA8VpR3nWRrjG6GtGjBrQxQKFevnUTHKCfPR4IvrW3kCgQ==} + /@rollup/rollup-darwin-x64@4.14.1: + resolution: {integrity: sha512-2pYRzEjVqq2TB/UNv47BV/8vQiXkFGVmPFwJb+1E0IFFZbIX8/jo1olxqqMbo6xCXf8kabANhp5bzCij2tFLUA==} cpu: [x64] os: [darwin] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-arm-gnueabihf@4.14.0: - resolution: {integrity: sha512-ygrGVhQP47mRh0AAD0zl6QqCbNsf0eTo+vgwkY6LunBcg0f2Jv365GXlDUECIyoXp1kKwL5WW6rsO429DBY/bA==} + /@rollup/rollup-linux-arm-gnueabihf@4.14.1: + resolution: {integrity: sha512-mS6wQ6Do6/wmrF9aTFVpIJ3/IDXhg1EZcQFYHZLHqw6AzMBjTHWnCG35HxSqUNphh0EHqSM6wRTT8HsL1C0x5g==} cpu: [arm] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-arm64-gnu@4.14.0: - resolution: {integrity: sha512-x+uJ6MAYRlHGe9wi4HQjxpaKHPM3d3JjqqCkeC5gpnnI6OWovLdXTpfa8trjxPLnWKyBsSi5kne+146GAxFt4A==} + /@rollup/rollup-linux-arm64-gnu@4.14.1: + resolution: {integrity: sha512-p9rGKYkHdFMzhckOTFubfxgyIO1vw//7IIjBBRVzyZebWlzRLeNhqxuSaZ7kCEKVkm/kuC9fVRW9HkC/zNRG2w==} cpu: [arm64] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-arm64-musl@4.14.0: - resolution: {integrity: sha512-nrRw8ZTQKg6+Lttwqo6a2VxR9tOroa2m91XbdQ2sUUzHoedXlsyvY1fN4xWdqz8PKmf4orDwejxXHjh7YBGUCA==} + /@rollup/rollup-linux-arm64-musl@4.14.1: + resolution: {integrity: sha512-nDY6Yz5xS/Y4M2i9JLQd3Rofh5OR8Bn8qe3Mv/qCVpHFlwtZSBYSPaU4mrGazWkXrdQ98GB//H0BirGR/SKFSw==} cpu: [arm64] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-powerpc64le-gnu@4.14.0: - resolution: {integrity: sha512-xV0d5jDb4aFu84XKr+lcUJ9y3qpIWhttO3Qev97z8DKLXR62LC3cXT/bMZXrjLF9X+P5oSmJTzAhqwUbY96PnA==} + /@rollup/rollup-linux-powerpc64le-gnu@4.14.1: + resolution: {integrity: sha512-im7HE4VBL+aDswvcmfx88Mp1soqL9OBsdDBU8NqDEYtkri0qV0THhQsvZtZeNNlLeCUQ16PZyv7cqutjDF35qw==} cpu: [ppc64le] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-riscv64-gnu@4.14.0: - resolution: {integrity: sha512-SDDhBQwZX6LPRoPYjAZWyL27LbcBo7WdBFWJi5PI9RPCzU8ijzkQn7tt8NXiXRiFMJCVpkuMkBf4OxSxVMizAw==} + /@rollup/rollup-linux-riscv64-gnu@4.14.1: + resolution: {integrity: sha512-RWdiHuAxWmzPJgaHJdpvUUlDz8sdQz4P2uv367T2JocdDa98iRw2UjIJ4QxSyt077mXZT2X6pKfT2iYtVEvOFw==} cpu: [riscv64] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-s390x-gnu@4.14.0: - resolution: {integrity: sha512-RxB/qez8zIDshNJDufYlTT0ZTVut5eCpAZ3bdXDU9yTxBzui3KhbGjROK2OYTTor7alM7XBhssgoO3CZ0XD3qA==} + /@rollup/rollup-linux-s390x-gnu@4.14.1: + resolution: {integrity: sha512-VMgaGQ5zRX6ZqV/fas65/sUGc9cPmsntq2FiGmayW9KMNfWVG/j0BAqImvU4KTeOOgYSf1F+k6at1UfNONuNjA==} cpu: [s390x] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-x64-gnu@4.14.0: - resolution: {integrity: sha512-C6y6z2eCNCfhZxT9u+jAM2Fup89ZjiG5pIzZIDycs1IwESviLxwkQcFRGLjnDrP+PT+v5i4YFvlcfAs+LnreXg==} + /@rollup/rollup-linux-x64-gnu@4.14.1: + resolution: {integrity: sha512-9Q7DGjZN+hTdJomaQ3Iub4m6VPu1r94bmK2z3UeWP3dGUecRC54tmVu9vKHTm1bOt3ASoYtEz6JSRLFzrysKlA==} cpu: [x64] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-x64-musl@4.14.0: - resolution: {integrity: sha512-i0QwbHYfnOMYsBEyjxcwGu5SMIi9sImDVjDg087hpzXqhBSosxkE7gyIYFHgfFl4mr7RrXksIBZ4DoLoP4FhJg==} + /@rollup/rollup-linux-x64-musl@4.14.1: + resolution: {integrity: sha512-JNEG/Ti55413SsreTguSx0LOVKX902OfXIKVg+TCXO6Gjans/k9O6ww9q3oLGjNDaTLxM+IHFMeXy/0RXL5R/g==} cpu: [x64] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-win32-arm64-msvc@4.14.0: - resolution: {integrity: sha512-Fq52EYb0riNHLBTAcL0cun+rRwyZ10S9vKzhGKKgeD+XbwunszSY0rVMco5KbOsTlwovP2rTOkiII/fQ4ih/zQ==} + /@rollup/rollup-win32-arm64-msvc@4.14.1: + resolution: {integrity: sha512-ryS22I9y0mumlLNwDFYZRDFLwWh3aKaC72CWjFcFvxK0U6v/mOkM5Up1bTbCRAhv3kEIwW2ajROegCIQViUCeA==} cpu: [arm64] os: [win32] requiresBuild: true dev: true optional: true - /@rollup/rollup-win32-ia32-msvc@4.14.0: - resolution: {integrity: sha512-e/PBHxPdJ00O9p5Ui43+vixSgVf4NlLsmV6QneGERJ3lnjIua/kim6PRFe3iDueT1rQcgSkYP8ZBBXa/h4iPvw==} + /@rollup/rollup-win32-ia32-msvc@4.14.1: + resolution: {integrity: sha512-TdloItiGk+T0mTxKx7Hp279xy30LspMso+GzQvV2maYePMAWdmrzqSNZhUpPj3CGw12aGj57I026PgLCTu8CGg==} cpu: [ia32] os: [win32] requiresBuild: true dev: true optional: true - /@rollup/rollup-win32-x64-msvc@4.14.0: - resolution: {integrity: sha512-aGg7iToJjdklmxlUlJh/PaPNa4PmqHfyRMLunbL3eaMO0gp656+q1zOKkpJ/CVe9CryJv6tAN1HDoR8cNGzkag==} + /@rollup/rollup-win32-x64-msvc@4.14.1: + resolution: {integrity: sha512-wQGI+LY/Py20zdUPq+XCem7JcPOyzIJBm3dli+56DJsQOHbnXZFEwgmnC6el1TPAfC8lBT3m+z69RmLykNUbew==} cpu: [x64] os: [win32] requiresBuild: true @@ -675,19 +682,23 @@ packages: engines: {node: '>=10.13.0'} dev: false - /@types/eslint@8.56.7: - resolution: {integrity: sha512-SjDvI/x3zsZnOkYZ3lCt9lOZWZLB2jIlNKz+LBgCtDurK0JZcwucxYHn1w2BJkD34dgX9Tjnak0txtq4WTggEA==} + /@types/eslint@8.40.0: + resolution: {integrity: sha512-nbq2mvc/tBrK9zQQuItvjJl++GTN5j06DaPtp3hZCpngmG6Q3xoyEmd0TwZI0gAy/G1X0zhGBbr2imsGFdFV0g==} dependencies: - '@types/estree': 1.0.5 - '@types/json-schema': 7.0.15 + '@types/estree': 1.0.1 + '@types/json-schema': 7.0.12 + dev: true + + /@types/estree@1.0.1: + resolution: {integrity: sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==} dev: true /@types/estree@1.0.5: resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} dev: true - /@types/json-schema@7.0.15: - resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + /@types/json-schema@7.0.12: + resolution: {integrity: sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==} dev: true /@ungap/structured-clone@1.2.0: @@ -739,6 +750,10 @@ packages: '@vue/compiler-dom': 3.4.21 '@vue/shared': 3.4.21 + /@vue/devtools-api@6.5.0: + resolution: {integrity: sha512-o9KfBeaBmCKl10usN4crU53fYtC1r7jJwdGKjPT24t348rHxgfpZ0xL3Xm/gLUYnc0oTp8LAmrxOeLyu6tbk2Q==} + dev: false + /@vue/devtools-api@6.6.1: resolution: {integrity: sha512-LgPscpE3Vs0x96PzSSB4IGVSZXZBZHpfxs+ZA1d+VEPwHdOXowy/Y2CsvCAIFrf+ssVU1pD1jidj505EpUnfbA==} dev: false @@ -823,8 +838,8 @@ packages: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} dev: true - /binary-extensions@2.3.0: - resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + /binary-extensions@2.2.0: + resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} engines: {node: '>=8'} dev: true @@ -865,8 +880,8 @@ packages: '@kurkle/color': 0.3.2 dev: false - /chokidar@3.6.0: - resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + /chokidar@3.5.3: + resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} engines: {node: '>= 8.10.0'} dependencies: anymatch: 3.1.3 @@ -1136,9 +1151,9 @@ packages: file-entry-cache: 6.0.1 find-up: 5.0.0 glob-parent: 6.0.2 - globals: 13.24.0 + globals: 13.20.0 graphemer: 1.4.0 - ignore: 5.3.1 + ignore: 5.2.4 imurmurhash: 0.1.4 is-glob: 4.0.3 is-path-inside: 3.0.3 @@ -1203,8 +1218,8 @@ packages: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} dev: true - /fastq@1.17.1: - resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} + /fastq@1.15.0: + resolution: {integrity: sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==} dependencies: reusify: 1.0.4 dev: true @@ -1213,7 +1228,7 @@ packages: resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} engines: {node: ^10.12.0 || >=12.0.0} dependencies: - flat-cache: 3.2.0 + flat-cache: 3.0.4 dev: true /fill-range@7.0.1: @@ -1231,29 +1246,24 @@ packages: path-exists: 4.0.0 dev: true - /flat-cache@3.2.0: - resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} + /flat-cache@3.0.4: + resolution: {integrity: sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==} engines: {node: ^10.12.0 || >=12.0.0} dependencies: - flatted: 3.3.1 - keyv: 4.5.4 + flatted: 3.2.7 rimraf: 3.0.2 dev: true - /flatted@3.3.1: - resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==} + /flatted@3.2.7: + resolution: {integrity: sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==} dev: true - /floating-vue@2.0.0(vue@3.4.21): - resolution: {integrity: sha512-YSffLYOjoaaPPBZc7VQR2qMCQ7xeXuh7i8a2u8WOdSmkjTtKtZpj2aaJnLtZRHmehrMHyCgtSxLu8jFNNX2sVw==} + /floating-vue@2.0.0-beta.20(vue@3.4.21): + resolution: {integrity: sha512-N68otcpp6WwcYC7zP8GeJqNZVdfvS7tEY88lwmuAHeqRgnfWx1Un8enzLxROyVnBDZ3TwUoUdj5IFg+bUT7JeA==} peerDependencies: - '@nuxt/kit': ^3.2.0 vue: ^3.2.0 - peerDependenciesMeta: - '@nuxt/kit': - optional: true dependencies: - '@floating-ui/dom': 1.1.1 + '@floating-ui/dom': 0.1.10 vue: 3.4.21 vue-resize: 2.0.0-alpha.1(vue@3.4.21) dev: false @@ -1309,6 +1319,13 @@ packages: path-is-absolute: 1.0.1 dev: true + /globals@13.20.0: + resolution: {integrity: sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==} + engines: {node: '>=8'} + dependencies: + type-fest: 0.20.2 + dev: true + /globals@13.24.0: resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} engines: {node: '>=8'} @@ -1325,18 +1342,18 @@ packages: engines: {node: '>=8'} dev: true - /highlight.js@11.9.0: - resolution: {integrity: sha512-fJ7cW7fQGCYAkgv4CPfwFHrfd/cLS4Hau96JuJ+ZTOWhjnhoeN1ub1tFmALm/+lW5z4WCAuAV9bm05AP0mS6Gw==} + /highlight.js@11.8.0: + resolution: {integrity: sha512-MedQhoqVdr0U6SSnWPzfiadUcDHfN/Wzq25AkXiQv9oiOO/sG0S7XkvpFIqWBl9Yq1UYyYOOVORs5UW2XlPyzg==} engines: {node: '>=12.0.0'} dev: false - /ignore@5.3.1: - resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==} + /ignore@5.2.4: + resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==} engines: {node: '>= 4'} dev: true - /immutable@4.3.5: - resolution: {integrity: sha512-8eabxkth9gZatlwl5TBuJnCsoTADlL6ftEr7A4qgdaTsPyreilDSnUk57SO+jfKcNtxPa22U5KK6DSeAYhpBJw==} + /immutable@4.3.0: + resolution: {integrity: sha512-0AOCmOip+xgJwEVTQj1EfiDDOkPmuyllDuTuEX+DDXUgapLAsBIfkg3sxCYyCEA8mQqZrrxPUGjcOQ2JS3WLkg==} dev: true /import-fresh@3.3.0: @@ -1367,7 +1384,7 @@ packages: resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} engines: {node: '>=8'} dependencies: - binary-extensions: 2.3.0 + binary-extensions: 2.2.0 dev: true /is-extglob@2.1.1: @@ -1403,10 +1420,6 @@ packages: argparse: 2.0.1 dev: true - /json-buffer@3.0.1: - resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} - dev: true - /json-schema-traverse@0.4.1: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} dev: true @@ -1415,12 +1428,6 @@ packages: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} dev: true - /keyv@4.5.4: - resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} - dependencies: - json-buffer: 3.0.1 - dev: true - /levn@0.4.1: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} @@ -1463,8 +1470,8 @@ packages: dependencies: '@jridgewell/sourcemap-codec': 1.4.15 - /markdown-it@13.0.2: - resolution: {integrity: sha512-FtwnEuuK+2yVU7goGn/MJ0WBZMM9ZPgU9spqlFs7/A/pDIUNSOQZhUgOqYCficIuR2QaFnrt8LHqBWsbTAoI5w==} + /markdown-it@13.0.1: + resolution: {integrity: sha512-lTlxriVoy2criHP0JKRhO2VDG9c2ypWCsT237eDiLqi09rmbKoUetyGHq2uOIRoRS//kfoJckS0eUzzkDR+k2Q==} hasBin: true dependencies: argparse: 2.0.1 @@ -1540,17 +1547,16 @@ packages: dependencies: chart.js: 4.4.2 dayjs: 1.11.10 - floating-vue: 2.0.0(vue@3.4.21) - highlight.js: 11.9.0 - markdown-it: 13.0.2 - qrcode.vue: 3.4.1(vue@3.4.21) + floating-vue: 2.0.0-beta.20(vue@3.4.21) + highlight.js: 11.8.0 + markdown-it: 13.0.1 + qrcode.vue: 3.4.0(vue@3.4.21) vue: 3.4.21 - vue-chartjs: 5.3.0(chart.js@4.4.2)(vue@3.4.21) + vue-chartjs: 5.3.1(chart.js@4.4.2)(vue@3.4.21) vue-router: 4.3.0(vue@3.4.21) vue-select: 4.0.0-beta.6(vue@3.4.21) - xss: 1.0.15 + xss: 1.0.14 transitivePeerDependencies: - - '@nuxt/kit' - typescript dev: false @@ -1628,9 +1634,9 @@ packages: typescript: optional: true dependencies: - '@vue/devtools-api': 6.6.1 + '@vue/devtools-api': 6.5.0 vue: 3.4.21 - vue-demi: 0.14.7(vue@3.4.21) + vue-demi: 0.14.5(vue@3.4.21) dev: false /postcss-selector-parser@6.0.16: @@ -1660,13 +1666,13 @@ packages: hasBin: true dev: true - /punycode@2.3.1: - resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + /punycode@2.3.0: + resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==} engines: {node: '>=6'} dev: true - /qrcode.vue@3.4.1(vue@3.4.21): - resolution: {integrity: sha512-wq/zHsifH4FJ1GXQi8/wNxD1KfQkckIpjK1KPTc/qwYU5/Bkd4me0w4xZSg6EXk6xLBkVDE0zxVagewv5EMAVA==} + /qrcode.vue@3.4.0(vue@3.4.21): + resolution: {integrity: sha512-4XeImbv10Fin16Fl2DArCMhGyAdvIg2jb7vDT+hZiIAMg/6H6mz9nUZr/dR8jBcun5VzNzkiwKhiqOGbloinwA==} peerDependencies: vue: ^3.0.0 dependencies: @@ -1709,28 +1715,28 @@ packages: fsevents: 2.3.3 dev: true - /rollup@4.14.0: - resolution: {integrity: sha512-Qe7w62TyawbDzB4yt32R0+AbIo6m1/sqO7UPzFS8Z/ksL5mrfhA0v4CavfdmFav3D+ub4QeAgsGEe84DoWe/nQ==} + /rollup@4.14.1: + resolution: {integrity: sha512-4LnHSdd3QK2pa1J6dFbfm1HN0D7vSK/ZuZTsdyUAlA6Rr1yTouUTL13HaDOGJVgby461AhrNGBS7sCGXXtT+SA==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true dependencies: '@types/estree': 1.0.5 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.14.0 - '@rollup/rollup-android-arm64': 4.14.0 - '@rollup/rollup-darwin-arm64': 4.14.0 - '@rollup/rollup-darwin-x64': 4.14.0 - '@rollup/rollup-linux-arm-gnueabihf': 4.14.0 - '@rollup/rollup-linux-arm64-gnu': 4.14.0 - '@rollup/rollup-linux-arm64-musl': 4.14.0 - '@rollup/rollup-linux-powerpc64le-gnu': 4.14.0 - '@rollup/rollup-linux-riscv64-gnu': 4.14.0 - '@rollup/rollup-linux-s390x-gnu': 4.14.0 - '@rollup/rollup-linux-x64-gnu': 4.14.0 - '@rollup/rollup-linux-x64-musl': 4.14.0 - '@rollup/rollup-win32-arm64-msvc': 4.14.0 - '@rollup/rollup-win32-ia32-msvc': 4.14.0 - '@rollup/rollup-win32-x64-msvc': 4.14.0 + '@rollup/rollup-android-arm-eabi': 4.14.1 + '@rollup/rollup-android-arm64': 4.14.1 + '@rollup/rollup-darwin-arm64': 4.14.1 + '@rollup/rollup-darwin-x64': 4.14.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.14.1 + '@rollup/rollup-linux-arm64-gnu': 4.14.1 + '@rollup/rollup-linux-arm64-musl': 4.14.1 + '@rollup/rollup-linux-powerpc64le-gnu': 4.14.1 + '@rollup/rollup-linux-riscv64-gnu': 4.14.1 + '@rollup/rollup-linux-s390x-gnu': 4.14.1 + '@rollup/rollup-linux-x64-gnu': 4.14.1 + '@rollup/rollup-linux-x64-musl': 4.14.1 + '@rollup/rollup-win32-arm64-msvc': 4.14.1 + '@rollup/rollup-win32-ia32-msvc': 4.14.1 + '@rollup/rollup-win32-x64-msvc': 4.14.1 fsevents: 2.3.3 dev: true @@ -1745,9 +1751,9 @@ packages: engines: {node: '>=14.0.0'} hasBin: true dependencies: - chokidar: 3.6.0 - immutable: 4.3.5 - source-map-js: 1.2.0 + chokidar: 3.5.3 + immutable: 4.3.0 + source-map-js: 1.0.2 dev: true /semver@7.6.0: @@ -1775,6 +1781,11 @@ packages: engines: {node: '>=12'} dev: true + /source-map-js@1.0.2: + resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} + engines: {node: '>=0.10.0'} + dev: true + /source-map-js@1.2.0: resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} engines: {node: '>=0.10.0'} @@ -1798,8 +1809,8 @@ packages: has-flag: 4.0.0 dev: true - /svgo@3.2.0: - resolution: {integrity: sha512-4PP6CMW/V7l/GmKRKzsLR8xxjdHTV4IMvhTnpuHwwBazSIlw5W/5SmPjN8Dwyt7lKbSJrRDgp4t9ph0HgChFBQ==} + /svgo@3.0.2: + resolution: {integrity: sha512-Z706C1U2pb1+JGP48fbazf3KxHrWOsLme6Rv7imFBn5EnuanDW1GPaA/P1/dvObE670JDePC3mnj0k0B7P0jjQ==} engines: {node: '>=14.0.0'} hasBin: true dependencies: @@ -1807,7 +1818,6 @@ packages: commander: 7.2.0 css-select: 5.1.0 css-tree: 2.3.1 - css-what: 6.1.0 csso: 5.0.5 picocolors: 1.0.0 dev: false @@ -1850,7 +1860,7 @@ packages: /uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} dependencies: - punycode: 2.3.1 + punycode: 2.3.0 dev: true /util-deprecate@1.0.2: @@ -1864,7 +1874,7 @@ packages: vite: '>=2' dependencies: '@rollup/pluginutils': 4.2.1 - '@types/eslint': 8.56.7 + '@types/eslint': 8.40.0 eslint: 8.57.0 rollup: 2.79.1 vite: 5.2.8(sass@1.74.1) @@ -1875,7 +1885,7 @@ packages: peerDependencies: vue: '>=3.2.13' dependencies: - svgo: 3.2.0 + svgo: 3.0.2 vue: 3.4.21 dev: false @@ -1909,14 +1919,14 @@ packages: dependencies: esbuild: 0.20.2 postcss: 8.4.38 - rollup: 4.14.0 + rollup: 4.14.1 sass: 1.74.1 optionalDependencies: fsevents: 2.3.3 dev: true - /vue-chartjs@5.3.0(chart.js@4.4.2)(vue@3.4.21): - resolution: {integrity: sha512-8XqX0JU8vFZ+WA2/knz4z3ThClduni2Nm0BMe2u0mXgTfd9pXrmJ07QBI+WAij5P/aPmPMX54HCE1seWL37ZdQ==} + /vue-chartjs@5.3.1(chart.js@4.4.2)(vue@3.4.21): + resolution: {integrity: sha512-rZjqcHBxKiHrBl0CIvcOlVEBwRhpWAVf6rDU3vUfa7HuSRmGtCslc0Oc8m16oAVuk0erzc1FCtH1VCriHsrz+A==} peerDependencies: chart.js: ^4.1.1 vue: ^3.0.0-0 || ^2.7.0 @@ -1925,8 +1935,8 @@ packages: vue: 3.4.21 dev: false - /vue-demi@0.14.7(vue@3.4.21): - resolution: {integrity: sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==} + /vue-demi@0.14.5(vue@3.4.21): + resolution: {integrity: sha512-o9NUVpl/YlsGJ7t+xuqJKx8EBGf1quRhCiT6D/J0pfwmk9zUwYkC7yrF4SZCe6fETvSM3UNL2edcbYrSyc4QHA==} engines: {node: '>=12'} hasBin: true requiresBuild: true @@ -2038,8 +2048,8 @@ packages: engines: {node: '>=12'} dev: true - /xss@1.0.15: - resolution: {integrity: sha512-FVdlVVC67WOIPvfOwhoMETV72f6GbW7aOabBC3WxN/oUdoEMDyLz4OgRv5/gck2ZeNqEQu+Tb0kloovXOfpYVg==} + /xss@1.0.14: + resolution: {integrity: sha512-og7TEJhXvn1a7kzZGQ7ETjdQVS2UfZyTlsEdDOqvQF7GoxNfY+0YLCzBy1kPdsDDx4QuNAonQPddpsn6Xl/7sw==} engines: {node: '>= 0.10.0'} hasBin: true dependencies: diff --git a/theseus_gui/src-tauri/src/api/auth.rs b/theseus_gui/src-tauri/src/api/auth.rs index 95884bf33..b567db525 100644 --- a/theseus_gui/src-tauri/src/api/auth.rs +++ b/theseus_gui/src-tauri/src/api/auth.rs @@ -1,16 +1,15 @@ use crate::api::Result; +use chrono::{Duration, Utc}; use tauri::plugin::TauriPlugin; -use theseus::{hydra::init::DeviceLoginSuccess, prelude::*}; +use tauri::Manager; +use theseus::prelude::*; pub fn init() -> TauriPlugin { tauri::plugin::Builder::new("auth") .invoke_handler(tauri::generate_handler![ - auth_authenticate_begin_flow, - auth_authenticate_await_completion, - auth_cancel_flow, - auth_refresh, + auth_get_default_user, + auth_set_default_user, auth_remove_user, - auth_has_user, auth_users, auth_get_user, ]) @@ -20,47 +19,73 @@ pub fn init() -> TauriPlugin { /// Authenticate a user with Hydra - part 1 /// This begins the authentication flow quasi-synchronously, returning a URL to visit (that the user will sign in at) #[tauri::command] -pub async fn auth_authenticate_begin_flow() -> Result { - Ok(auth::authenticate_begin_flow().await?) -} +pub async fn auth_login(app: tauri::AppHandle) -> Result> { + let flow = minecraft_auth::begin_login().await?; -/// Authenticate a user with Hydra - part 2 -/// This completes the authentication flow quasi-synchronously, returning the sign-in credentials -/// (and also adding the credentials to the state) -#[tauri::command] -pub async fn auth_authenticate_await_completion() -> Result { - Ok(auth::authenticate_await_complete_flow().await?) -} + let start = Utc::now(); -#[tauri::command] -pub async fn auth_cancel_flow() -> Result<()> { - Ok(auth::cancel_flow().await?) -} + if let Some(window) = app.get_window("signin") { + window.close()?; + } + + let window = tauri::WindowBuilder::new( + &app, + "signin", + tauri::WindowUrl::External(flow.redirect_uri.parse().map_err( + |_| { + theseus::ErrorKind::OtherError( + "Error parsing auth redirect URL".to_string(), + ) + .as_error() + }, + )?), + ) + .title("Sign into Modrinth") + .build()?; -/// Refresh some credentials using Hydra, if needed -// invoke('plugin:auth|auth_refresh',user) + while (Utc::now() - start) < Duration::minutes(10) { + if window + .url() + .as_str() + .starts_with("https://login.live.com/oauth20_desktop.srf") + { + if let Some((_, code)) = + window.url().query_pairs().find(|x| x.0 == "code") + { + window.close()?; + let val = + minecraft_auth::finish_login(&code.clone(), flow).await?; + + return Ok(Some(val)); + } + } + + tokio::time::sleep(std::time::Duration::from_millis(50)).await; + } + + window.close()?; + Ok(None) +} #[tauri::command] -pub async fn auth_refresh(user: uuid::Uuid) -> Result { - Ok(auth::refresh(user).await?) +pub async fn auth_remove_user(user: uuid::Uuid) -> Result<()> { + Ok(minecraft_auth::remove_user(user).await?) } #[tauri::command] -pub async fn auth_remove_user(user: uuid::Uuid) -> Result<()> { - Ok(auth::remove_user(user).await?) +pub async fn auth_get_default_user() -> Result> { + Ok(minecraft_auth::get_default_user().await?) } -/// Check if a user exists in Theseus -// invoke('plugin:auth|auth_has_user',user) #[tauri::command] -pub async fn auth_has_user(user: uuid::Uuid) -> Result { - Ok(auth::has_user(user).await?) +pub async fn auth_set_default_user(user: uuid::Uuid) -> Result<()> { + Ok(minecraft_auth::set_default_user(user).await?) } /// Get a copy of the list of all user credentials // invoke('plugin:auth|auth_users',user) #[tauri::command] pub async fn auth_users() -> Result> { - Ok(auth::users().await?) + Ok(minecraft_auth::users().await?) } /// Get a user from the UUID @@ -68,5 +93,5 @@ pub async fn auth_users() -> Result> { // invoke('plugin:auth|auth_users',user) #[tauri::command] pub async fn auth_get_user(user: uuid::Uuid) -> Result { - Ok(auth::get_user(user).await?) + Ok(minecraft_auth::get_user(user).await?) } diff --git a/theseus_gui/src-tauri/src/api/mod.rs b/theseus_gui/src-tauri/src/api/mod.rs index d2a6b0757..7602317de 100644 --- a/theseus_gui/src-tauri/src/api/mod.rs +++ b/theseus_gui/src-tauri/src/api/mod.rs @@ -35,6 +35,9 @@ pub enum TheseusSerializableError { #[error("IO error: {0}")] IO(#[from] std::io::Error), + #[error("Tauri error: {0}")] + Tauri(#[from] tauri::Error), + #[cfg(target_os = "macos")] #[error("Callback error: {0}")] Callback(String), @@ -88,9 +91,12 @@ macro_rules! impl_serialize { #[cfg(target_os = "macos")] impl_serialize! { IO, + Tauri, Callback } + #[cfg(not(target_os = "macos"))] impl_serialize! { IO, + Tauri, } diff --git a/theseus_gui/src-tauri/src/main.rs b/theseus_gui/src-tauri/src/main.rs index 3a3fa0240..73d12d660 100644 --- a/theseus_gui/src-tauri/src/main.rs +++ b/theseus_gui/src-tauri/src/main.rs @@ -146,6 +146,7 @@ fn main() { initialize_state, is_dev, toggle_decorations, + api::auth::auth_login, ]); builder diff --git a/theseus_gui/src/App.vue b/theseus_gui/src/App.vue index 89e6fde6a..b2368b381 100644 --- a/theseus_gui/src/App.vue +++ b/theseus_gui/src/App.vue @@ -20,6 +20,7 @@ import { get } from '@/helpers/settings' import Breadcrumbs from '@/components/ui/Breadcrumbs.vue' import RunningAppBar from '@/components/ui/RunningAppBar.vue' import SplashScreen from '@/components/ui/SplashScreen.vue' +import ErrorModal from '@/components/ui/ErrorModal.vue' import ModrinthLoadingIndicator from '@/components/modrinth-loading-indicator' import { handleError, useNotifications } from '@/store/notifications.js' import { offline_listener, command_listener, warning_listener } from '@/helpers/events.js' @@ -40,15 +41,14 @@ import { TauriEvent } from '@tauri-apps/api/event' import { await_sync, check_safe_loading_bars_complete } from './helpers/state' import { confirm } from '@tauri-apps/api/dialog' import URLConfirmModal from '@/components/ui/URLConfirmModal.vue' -import StickyTitleBar from '@/components/ui/tutorial/StickyTitleBar.vue' import OnboardingScreen from '@/components/ui/tutorial/OnboardingScreen.vue' import { install_from_file } from './helpers/pack' +import { useError } from '@/store/error.js' const themeStore = useTheming() const urlModal = ref(null) const isLoading = ref(true) -const videoPlaying = ref(false) const offline = ref(false) const showOnboarding = ref(false) const nativeDecorations = ref(false) @@ -71,7 +71,6 @@ defineExpose({ } = await get() // video should play if the user is not on linux, and has not onboarded os.value = await getOS() - videoPlaying.value = !fully_onboarded && os.value !== 'Linux' const dev = await isDev() const version = await getVersion() showOnboarding.value = !fully_onboarded @@ -180,12 +179,19 @@ const isOnBrowse = computed(() => route.path.startsWith('/browse')) const loading = useLoading() const notifications = useNotifications() -const notificationsWrapper = ref(null) +const notificationsWrapper = ref() watch(notificationsWrapper, () => { notifications.setNotifs(notificationsWrapper.value) }) +const error = useError() +const errorModal = ref() + +watch(errorModal, () => { + error.setErrorModal(errorModal.value) +}) + document.querySelector('body').addEventListener('click', function (e) { let target = e.target while (target != null) { @@ -245,15 +251,6 @@ command_listener(async (e) => { diff --git a/theseus_gui/src/components/ui/RunningAppBar.vue b/theseus_gui/src/components/ui/RunningAppBar.vue index 037baed11..a8f39964b 100644 --- a/theseus_gui/src/components/ui/RunningAppBar.vue +++ b/theseus_gui/src/components/ui/RunningAppBar.vue @@ -1,6 +1,6 @@ diff --git a/theseus_gui/src/components/ui/tutorial/OnboardingScreen.vue b/theseus_gui/src/components/ui/tutorial/OnboardingScreen.vue index a30c956a7..f9beaa5c5 100644 --- a/theseus_gui/src/components/ui/tutorial/OnboardingScreen.vue +++ b/theseus_gui/src/components/ui/tutorial/OnboardingScreen.vue @@ -1,28 +1,6 @@ diff --git a/theseus_gui/src/components/ui/tutorial/PreImportScreen.vue b/theseus_gui/src/components/ui/tutorial/PreImportScreen.vue deleted file mode 100644 index 0fd26b16a..000000000 --- a/theseus_gui/src/components/ui/tutorial/PreImportScreen.vue +++ /dev/null @@ -1,184 +0,0 @@ - - - - - diff --git a/theseus_gui/src/components/ui/tutorial/TutorialTip.vue b/theseus_gui/src/components/ui/tutorial/TutorialTip.vue deleted file mode 100644 index 23a063a09..000000000 --- a/theseus_gui/src/components/ui/tutorial/TutorialTip.vue +++ /dev/null @@ -1,72 +0,0 @@ - - - - - diff --git a/theseus_gui/src/helpers/auth.js b/theseus_gui/src/helpers/auth.js index 08e6113c3..cf228c8d8 100644 --- a/theseus_gui/src/helpers/auth.js +++ b/theseus_gui/src/helpers/auth.js @@ -18,28 +18,20 @@ import { invoke } from '@tauri-apps/api/tauri' /// This returns a DeviceLoginSuccess object, with two relevant fields: /// - verification_uri: the URL to go to to complete the flow /// - user_code: the code to enter on the verification_uri page -export async function authenticate_begin_flow() { - return await invoke('plugin:auth|auth_authenticate_begin_flow') +export async function login() { + return await invoke('auth_login') } -/// Authenticate a user with Hydra - part 2 -/// This completes the authentication flow quasi-synchronously, returning the sign-in credentials -/// (and also adding the credentials to the state) -/// This returns a Credentials object -export async function authenticate_await_completion() { - return await invoke('plugin:auth|auth_authenticate_await_completion') -} - -export async function cancel_flow() { - return await invoke('plugin:auth|auth_cancel_flow') +/// Retrieves the default user +/// user is UUID +export async function get_default_user() { + return await invoke('plugin:auth|auth_get_default_user') } -/// Refresh some credentials using Hydra, if needed +/// Updates the default user /// user is UUID -/// update_name is bool -/// Returns a Credentials object -export async function refresh(user, update_name) { - return await invoke('plugin:auth|auth_refresh', { user, update_name }) +export async function set_default_user(user) { + return await invoke('plugin:auth|auth_set_default_user', { user }) } /// Remove a user account from the database @@ -48,13 +40,6 @@ export async function remove_user(user) { return await invoke('plugin:auth|auth_remove_user', { user }) } -// Add a path as a profile in-memory -// user is UUID -/// Returns a bool -export async function has_user(user) { - return await invoke('plugin:auth|auth_has_user', { user }) -} - /// Returns a list of users /// Returns an Array of Credentials export async function users() { diff --git a/theseus_gui/src/store/error.js b/theseus_gui/src/store/error.js new file mode 100644 index 000000000..859f2e667 --- /dev/null +++ b/theseus_gui/src/store/error.js @@ -0,0 +1,21 @@ +import { defineStore } from 'pinia' + +export const useError = defineStore('errorsStore', { + state: () => ({ + errorModal: null, + }), + actions: { + setErrorModal(ref) { + this.errorModal = ref + }, + showError(error) { + this.errorModal.show(error) + }, + }, +}) + +export const handleSevereError = (err) => { + const error = useError() + error.showError(err) + console.error(err) +} diff --git a/theseus_playground/src/main.rs b/theseus_playground/src/main.rs index 607476a2b..6e21dd152 100644 --- a/theseus_playground/src/main.rs +++ b/theseus_playground/src/main.rs @@ -14,15 +14,20 @@ use theseus::profile::create::profile_create; // 3) call the authenticate_await_complete_flow() function to get the credentials (like you would in the frontend) pub async fn authenticate_run() -> theseus::Result { println!("A browser window will now open, follow the login flow there."); - let login = auth::authenticate_begin_flow().await?; + let login = minecraft_auth::begin_login().await?; - println!("URL {}", login.verification_uri.as_str()); - println!("Code {}", login.user_code.as_str()); - webbrowser::open(login.verification_uri.as_str()) - .map_err(|e| IOError::with_path(e, login.verification_uri.as_str()))?; + println!("URL {}", login.redirect_uri.as_str()); + webbrowser::open(login.redirect_uri.as_str())?; - let credentials = auth::authenticate_await_complete_flow().await?; - State::sync().await?; + println!("Please enter URL code: "); + let mut input = String::new(); + std::io::stdin() + .read_line(&mut input) + .expect("error: unable to read user input"); + + println!("You entered: {}", input.trim()); + + let credentials = minecraft_auth::finish_login(&input, login).await?; println!("Logged in user {}.", credentials.username); Ok(credentials) @@ -38,6 +43,11 @@ async fn main() -> theseus::Result<()> { let st = State::get().await?; //State::update(); + if minecraft_auth::users().await?.is_empty() { + println!("No users found, authenticating."); + authenticate_run().await?; // could take credentials from here direct, but also deposited in state users + } + // Autodetect java globals let jres = jre::get_all_jre().await?; let java_8 = jre::find_filtered_jres("1.8", jres.clone(), false).await?; @@ -91,12 +101,6 @@ async fn main() -> theseus::Result<()> { State::sync().await?; - // Attempt to run game - if auth::users().await?.is_empty() { - println!("No users found, authenticating."); - authenticate_run().await?; // could take credentials from here direct, but also deposited in state users - } - println!("running"); // Run a profile, running minecraft and store the RwLock to the process let proc_lock = profile::run(&profile_path).await?; From 49cecf837b9f5bf2858964d26467bd2415858329 Mon Sep 17 00:00:00 2001 From: Geometrically <18202329+Geometrically@users.noreply.github.com> Date: Thu, 18 Apr 2024 20:28:52 -0700 Subject: [PATCH 3/7] Fix java installs (#1123) * Fix java installs * Finish java installs --- theseus/src/api/jre.rs | 78 ++++--------------- theseus/src/launcher/mod.rs | 72 ++++++++++------- theseus/src/state/mod.rs | 3 +- theseus/src/state/settings.rs | 38 --------- theseus_gui/src-tauri/src/api/jre.rs | 42 +--------- .../src/components/ui/JavaDetectionModal.vue | 20 ++--- .../src/components/ui/JavaSelector.vue | 27 ++----- .../src/components/ui/RunningAppBar.vue | 11 +++ .../ui/tutorial/OnboardingScreen.vue | 27 +------ theseus_gui/src/helpers/jre.js | 46 +---------- theseus_gui/src/pages/Settings.vue | 35 ++++----- theseus_playground/src/main.rs | 10 --- 12 files changed, 104 insertions(+), 305 deletions(-) diff --git a/theseus/src/api/jre.rs b/theseus/src/api/jre.rs index 915c46ba6..e3e2014d0 100644 --- a/theseus/src/api/jre.rs +++ b/theseus/src/api/jre.rs @@ -10,64 +10,32 @@ use crate::util::fetch::{fetch_advanced, fetch_json}; use crate::util::io; use crate::util::jre::extract_java_majorminor_version; use crate::{ - state::JavaGlobals, util::jre::{self, JavaVersion}, LoadingBarType, State, }; -pub const JAVA_8_KEY: &str = "JAVA_8"; -pub const JAVA_17_KEY: &str = "JAVA_17"; -pub const JAVA_18PLUS_KEY: &str = "JAVA_18PLUS"; - -// Autodetect JavaSettings default -// Using the supplied JavaVersions, autodetects the default JavaSettings -// Make a guess for what the default Java global settings should be -// Since the JRE paths are passed in as args, this handles the logic for selection. Currently this just pops the last one found -// TODO: When tauri compiler issue is fixed, this can be be improved (ie: getting JREs in-function) -pub async fn autodetect_java_globals( - mut java_8: Vec, - mut java_17: Vec, - mut java_18plus: Vec, -) -> crate::Result { - // Simply select last one found for initial guess - let mut java_globals = JavaGlobals::new(); - if let Some(jre) = java_8.pop() { - java_globals.insert(JAVA_8_KEY.to_string(), jre); - } - if let Some(jre) = java_17.pop() { - java_globals.insert(JAVA_17_KEY.to_string(), jre); - } - if let Some(jre) = java_18plus.pop() { - java_globals.insert(JAVA_18PLUS_KEY.to_string(), jre); - } - - Ok(java_globals) -} - // Searches for jres on the system given a java version (ex: 1.8, 1.17, 1.18) // Allow higher allows for versions higher than the given version to be returned ('at least') pub async fn find_filtered_jres( - version: &str, - jres: Vec, - allow_higher: bool, + java_version: Option, ) -> crate::Result> { - let version = extract_java_majorminor_version(version)?; + let jres = jre::get_all_jre().await?; + // Filter out JREs that are not 1.17 or higher - Ok(jres - .into_iter() - .filter(|jre| { - let jre_version = extract_java_majorminor_version(&jre.version); - if let Ok(jre_version) = jre_version { - if allow_higher { - jre_version >= version + Ok(if let Some(java_version) = java_version { + jres.into_iter() + .filter(|jre| { + let jre_version = extract_java_majorminor_version(&jre.version); + if let Ok(jre_version) = jre_version { + jre_version.1 == java_version } else { - jre_version == version + false } - } else { - false - } - }) - .collect()) + }) + .collect() + } else { + jres + }) } #[theseus_macros::debug_pin] @@ -176,17 +144,6 @@ pub async fn auto_install_java(java_version: u32) -> crate::Result { } } -// Get all JREs that exist on the system -pub async fn get_all_jre() -> crate::Result> { - Ok(jre::get_all_jre().await?) -} - -pub async fn validate_globals() -> crate::Result { - let state = State::get().await?; - let settings = state.settings.read().await; - Ok(settings.java_globals.is_all_valid().await) -} - // Validates JRE at a given at a given path pub async fn check_jre(path: PathBuf) -> crate::Result> { Ok(jre::check_java_at_filepath(&path).await) @@ -196,14 +153,13 @@ pub async fn check_jre(path: PathBuf) -> crate::Result> { pub async fn test_jre( path: PathBuf, major_version: u32, - minor_version: u32, ) -> crate::Result { let jre = match jre::check_java_at_filepath(&path).await { Some(jre) => jre, None => return Ok(false), }; - let (major, minor) = extract_java_majorminor_version(&jre.version)?; - Ok(major == major_version && minor == minor_version) + let (major, _) = extract_java_majorminor_version(&jre.version)?; + Ok(major == major_version) } // Gets maximum memory in KiB. diff --git a/theseus/src/launcher/mod.rs b/theseus/src/launcher/mod.rs index 9507a52b9..e1ffef964 100644 --- a/theseus/src/launcher/mod.rs +++ b/theseus/src/launcher/mod.rs @@ -1,7 +1,6 @@ //! Logic for launching Minecraft use crate::event::emit::{emit_loading, init_or_edit_loading}; use crate::event::{LoadingBarId, LoadingBarType}; -use crate::jre::{self, JAVA_17_KEY, JAVA_18PLUS_KEY, JAVA_8_KEY}; use crate::launcher::io::IOError; use crate::prelude::JavaVersion; use crate::state::{Credentials, ProfileInstallStage}; @@ -118,24 +117,17 @@ pub async fn get_java_version_from_profile( if let Some(java) = profile.java.clone().and_then(|x| x.override_version) { Ok(Some(java)) } else { - let optimal_keys = match version_info + let key = version_info .java_version .as_ref() .map(|it| it.major_version) - .unwrap_or(8) - { - 0..=15 => vec![JAVA_8_KEY, JAVA_17_KEY, JAVA_18PLUS_KEY], - 16..=17 => vec![JAVA_17_KEY, JAVA_18PLUS_KEY], - _ => vec![JAVA_18PLUS_KEY], - }; + .unwrap_or(8); let state = State::get().await?; let settings = state.settings.read().await; - for key in optimal_keys { - if let Some(java) = settings.java_globals.get(&key.to_string()) { - return Ok(Some(java.clone())); - } + if let Some(java) = settings.java_globals.get(&format!("JAVA_{key}")) { + return Ok(Some(java.clone())); } Ok(None) @@ -215,24 +207,43 @@ pub async fn install_minecraft( ) .await?; - let java_version = get_java_version_from_profile(profile, &version_info) - .await? - .ok_or_else(|| { - crate::ErrorKind::OtherError( - "Missing correct java installation".to_string(), - ) - })?; + // TODO: check if java exists, if not install it add to install step + + let key = version_info + .java_version + .as_ref() + .map(|it| it.major_version) + .unwrap_or(8); + let (java_version, set_java) = if let Some(java_version) = + get_java_version_from_profile(profile, &version_info).await? + { + (std::path::PathBuf::from(java_version.path), false) + } else { + let path = crate::api::jre::auto_install_java(key).await?; + + (path, true) + }; // Test jre version - let java_version = jre::check_jre(java_version.path.clone().into()) + let java_version = crate::api::jre::check_jre(java_version.clone()) .await? .ok_or_else(|| { crate::ErrorKind::LauncherError(format!( - "Java path invalid or non-functional: {}", - java_version.path + "Java path invalid or non-functional: {:?}", + java_version )) })?; + if set_java { + { + let mut settings = state.settings.write().await; + settings + .java_globals + .insert(format!("JAVA_{key}"), java_version.clone()); + } + State::sync().await?; + } + // Download minecraft (5-90) download::download_minecraft( &state, @@ -434,14 +445,15 @@ pub async fn launch_minecraft( })?; // Test jre version - let java_version = jre::check_jre(java_version.path.clone().into()) - .await? - .ok_or_else(|| { - crate::ErrorKind::LauncherError(format!( - "Java path invalid or non-functional: {}", - java_version.path - )) - })?; + let java_version = + crate::api::jre::check_jre(java_version.path.clone().into()) + .await? + .ok_or_else(|| { + crate::ErrorKind::LauncherError(format!( + "Java path invalid or non-functional: {}", + java_version.path + )) + })?; let client_path = state .directories diff --git a/theseus/src/state/mod.rs b/theseus/src/state/mod.rs index a0e510e0c..fb7928a2c 100644 --- a/theseus/src/state/mod.rs +++ b/theseus/src/state/mod.rs @@ -244,10 +244,9 @@ impl State { let res2 = Tags::update(); let res3 = Metadata::update(); let res4 = Profiles::update_projects(); - let res5 = Settings::update_java(); let res6 = CredentialsStore::update_creds(); - let _ = join!(res1, res2, res3, res4, res5, res6); + let _ = join!(res1, res2, res3, res4, res6); } } }); diff --git a/theseus/src/state/settings.rs b/theseus/src/state/settings.rs index 6d04e4bac..8d999c902 100644 --- a/theseus/src/state/settings.rs +++ b/theseus/src/state/settings.rs @@ -1,8 +1,4 @@ //! Theseus settings file -use crate::{ - jre::{self, autodetect_java_globals, find_filtered_jres}, - State, -}; use serde::{Deserialize, Serialize}; use std::path::{Path, PathBuf}; use tokio::fs; @@ -116,40 +112,6 @@ impl Settings { } } - #[tracing::instrument] - #[theseus_macros::debug_pin] - pub async fn update_java() { - let res = async { - let state = State::get().await?; - let settings_read = state.settings.write().await; - - if settings_read.java_globals.count() == 0 { - drop(settings_read); - let jres = jre::get_all_jre().await?; - let java_8 = - find_filtered_jres("1.8", jres.clone(), false).await?; - let java_17 = - find_filtered_jres("1.17", jres.clone(), false).await?; - let java_18plus = - find_filtered_jres("1.18", jres.clone(), true).await?; - let java_globals = - autodetect_java_globals(java_8, java_17, java_18plus) - .await?; - state.settings.write().await.java_globals = java_globals; - } - - Ok::<(), crate::Error>(()) - } - .await; - - match res { - Ok(()) => {} - Err(err) => { - tracing::warn!("Unable to update launcher java: {err}") - } - }; - } - #[tracing::instrument(skip(self))] pub async fn sync(&self, to: &Path) -> crate::Result<()> { fs::write(to, serde_json::to_vec(self)?) diff --git a/theseus_gui/src-tauri/src/api/jre.rs b/theseus_gui/src-tauri/src/api/jre.rs index 3514e294b..89f057513 100644 --- a/theseus_gui/src-tauri/src/api/jre.rs +++ b/theseus_gui/src-tauri/src/api/jre.rs @@ -8,10 +8,7 @@ use theseus::prelude::*; pub fn init() -> TauriPlugin { tauri::plugin::Builder::new("jre") .invoke_handler(tauri::generate_handler![ - jre_get_all_jre, jre_find_filtered_jres, - jre_autodetect_java_globals, - jre_validate_globals, jre_get_jre, jre_test_jre, jre_auto_install_java, @@ -20,39 +17,12 @@ pub fn init() -> TauriPlugin { .build() } -/// Get all JREs that exist on the system -#[tauri::command] -pub async fn jre_get_all_jre() -> Result> { - Ok(jre::get_all_jre().await?) -} - // Finds the installation of Java 8, if it exists #[tauri::command] pub async fn jre_find_filtered_jres( - jres: Vec, - version: String, - allow_higher: bool, + version: Option, ) -> Result> { - Ok(jre::find_filtered_jres(&version, jres, allow_higher).await?) -} - -// Autodetect Java globals, by searching the users computer. -// Selects from the given JREs, and returns a new JavaGlobals -// Returns a *NEW* JavaGlobals that can be put into Settings -#[tauri::command] -pub async fn jre_autodetect_java_globals( - java_8: Vec, - java_17: Vec, - java_18plus: Vec, -) -> Result { - Ok(jre::autodetect_java_globals(java_8, java_17, java_18plus).await?) -} - -// Validates java globals, by checking if the paths exist -// If false, recommend to direct them to reassign, or to re-guess -#[tauri::command] -pub async fn jre_validate_globals() -> Result { - Ok(jre::validate_globals().await?) + Ok(jre::find_filtered_jres(version).await?) } // Validates JRE at a given path @@ -64,12 +34,8 @@ pub async fn jre_get_jre(path: PathBuf) -> Result> { // Tests JRE of a certain version #[tauri::command] -pub async fn jre_test_jre( - path: PathBuf, - major_version: u32, - minor_version: u32, -) -> Result { - Ok(jre::test_jre(path, major_version, minor_version).await?) +pub async fn jre_test_jre(path: PathBuf, major_version: u32) -> Result { + Ok(jre::test_jre(path, major_version).await?) } // Auto installs java for the given java version diff --git a/theseus_gui/src/components/ui/JavaDetectionModal.vue b/theseus_gui/src/components/ui/JavaDetectionModal.vue index 568e09e3b..0a60dbc5d 100644 --- a/theseus_gui/src/components/ui/JavaDetectionModal.vue +++ b/theseus_gui/src/components/ui/JavaDetectionModal.vue @@ -37,12 +37,7 @@ +
+
+
+ + Installed the app before April 23rd, 2024? +
+
+ Modrinth has updated our sign-in workflow to allow for better stability, security, and + performance. You must sign in again so your credentials can be upgraded to this new + flow. +
+
+

+ To play this instance, you must sign in through Microsoft below. If you don't have a + Minecraft account, you can purchase the game on the + Minecraft website. +

+
+ +
+
Get support - +
+ +