From 0095fc566ee628e001ddaaafb96d649ce0a7956d Mon Sep 17 00:00:00 2001 From: Kasper Seweryn Date: Thu, 15 Feb 2024 21:30:41 +0100 Subject: [PATCH] feat(tauri): offload OAuth login flow to a separate window Part-of: --- front/src/components/auth/LoginForm.vue | 61 ++++++++++--------------- front/src/store/auth.ts | 59 +++++++++++++++++++++--- front/tauri/Cargo.lock | 60 ++++++++++++++++++++++++ front/tauri/Cargo.toml | 3 +- front/tauri/capabilities/migrated.json | 2 +- front/tauri/capabilities/oauth.json | 8 ++++ front/tauri/rust-toolchain.toml | 4 ++ front/tauri/src/main.rs | 8 +++- front/tauri/tauri.conf.json | 5 +- 9 files changed, 160 insertions(+), 50 deletions(-) create mode 100644 front/tauri/capabilities/oauth.json create mode 100644 front/tauri/rust-toolchain.toml diff --git a/front/src/components/auth/LoginForm.vue b/front/src/components/auth/LoginForm.vue index 41be7710b..44e9d0f90 100644 --- a/front/src/components/auth/LoginForm.vue +++ b/front/src/components/auth/LoginForm.vue @@ -1,9 +1,9 @@ - diff --git a/front/src/store/auth.ts b/front/src/store/auth.ts index d2a3f1530..a4a9d364b 100644 --- a/front/src/store/auth.ts +++ b/front/src/store/auth.ts @@ -2,6 +2,7 @@ import type { BackendError, User } from '~/types' import type { Module } from 'vuex' import type { RootState } from '~/store/index' import type { RouteLocationRaw } from 'vue-router' +import type { WebviewWindow } from '@tauri-apps/api/webview' import axios from 'axios' import useLogger from '~/composables/useLogger' @@ -9,6 +10,7 @@ import useFormData from '~/composables/useFormData' import { clear as clearIDB } from 'idb-keyval' import { useQueue } from '~/composables/audio/queue' +import { isTauri } from '~/composables/tauri' export type Permission = 'settings' | 'library' | 'moderation' export interface State { @@ -21,6 +23,8 @@ export interface State { scopedTokens: ScopedTokens applicationSecret: string | undefined + + oauthWindow: WebviewWindow | undefined } interface ScopedTokens { @@ -55,7 +59,7 @@ function getDefaultOauth (): OAuthTokens { async function createOauthApp () { const payload = { - name: `Funkwhale web client at ${window.location.hostname}`, + name: `Funkwhale web client at ${location.hostname}`, website: location.origin, scopes: NEEDED_SCOPES, redirect_uris: `${location.origin}/auth/callback` @@ -78,7 +82,9 @@ const store: Module = { oauth: getDefaultOauth(), scopedTokens: getDefaultScopedTokens(), - applicationSecret: undefined + applicationSecret: undefined, + + oauthWindow: undefined }, getters: { header: state => { @@ -243,14 +249,41 @@ const store: Module = { commit('permission', { key: permission, status: hasPermission }) } }, - async oauthLogin ({ state, rootState, commit }, next: RouteLocationRaw) { + async tryFinishOAuthFlow ({ state }) { + if (isTauri()) { + return state.oauthWindow?.close().catch(() => { + // Ignore the error in case of window being already closed + }) + } + }, + async oauthLogin ({ state, rootState, commit, dispatch }, next: RouteLocationRaw) { const app = await createOauthApp() commit('oauthApp', app) - const redirectUri = encodeURIComponent(`${location.origin}/auth/callback`) - const params = `response_type=code&scope=${encodeURIComponent(NEEDED_SCOPES)}&redirect_uri=${redirectUri}&state=${next}&client_id=${state.oauth.clientId}` + const redirectUrl = encodeURIComponent(`${location.origin}/auth/callback`) + const params = `response_type=code&scope=${encodeURIComponent(NEEDED_SCOPES)}&redirect_uri=${redirectUrl}&state=${next}&client_id=${state.oauth.clientId}` const authorizeUrl = `${rootState.instance.instanceUrl}authorize?${params}` - logger.log('Redirecting user...', authorizeUrl) - window.location.href = authorizeUrl + + if (isTauri()) { + const { WebviewWindow } = await import('@tauri-apps/api/webview') + + state.oauthWindow = new WebviewWindow('oauth', { + title: `Login to ${rootState.instance.settings.instance.name}`, + parent: 'main', + url: authorizeUrl + }) + + const token = await new Promise((resolve, reject) => { + state.oauthWindow?.once('tauri://error', reject) + state.oauthWindow?.once('tauri://destroyed', () => reject(new Error('Aborted by user'))) + state.oauthWindow?.once('oauthToken', async (event) => resolve(event.payload)) + }).finally(() => dispatch('tryFinishOAuthFlow')) + + commit('oauthToken', token) + await dispatch('fetchUser') + } else { + logger.log('Redirecting user...', authorizeUrl) + location.href = authorizeUrl + } }, async handleOauthCallback ({ state, commit, dispatch }, authorizationCode) { logger.log('Fetching token...') @@ -266,6 +299,18 @@ const store: Module = { useFormData(payload), { headers: { 'Content-Type': 'multipart/form-data' } } ) + + if (isTauri()) { + const { getCurrent } = await import('@tauri-apps/api/window') + const currentWindow = getCurrent() + + // If the current window is the oauth window, pass the event to the main window + if (currentWindow.label === 'oauth') { + await currentWindow.emit('oauthToken', response.data) + return + } + } + commit('oauthToken', response.data) await dispatch('fetchUser') }, diff --git a/front/tauri/Cargo.lock b/front/tauri/Cargo.lock index 2e7413a6f..75c258109 100644 --- a/front/tauri/Cargo.lock +++ b/front/tauri/Cargo.lock @@ -567,6 +567,33 @@ dependencies = [ "objc", ] +[[package]] +name = "color-eyre" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a667583cca8c4f8436db8de46ea8233c42a7d9ae424a82d338f2e4675229204" +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" @@ -1009,6 +1036,16 @@ 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" @@ -1099,6 +1136,7 @@ dependencies = [ name = "funkwhale" version = "0.1.0" dependencies = [ + "color-eyre", "serde", "serde_json", "tauri", @@ -1714,6 +1752,12 @@ 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" @@ -2288,6 +2332,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" +[[package]] +name = "owo-colors" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" + [[package]] name = "pango" version = "0.18.3" @@ -3908,6 +3958,16 @@ dependencies = [ "valuable", ] +[[package]] +name = "tracing-error" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e" +dependencies = [ + "tracing", + "tracing-subscriber", +] + [[package]] name = "tracing-log" version = "0.2.0" diff --git a/front/tauri/Cargo.toml b/front/tauri/Cargo.toml index f90ecf30a..163ac6a59 100644 --- a/front/tauri/Cargo.toml +++ b/front/tauri/Cargo.toml @@ -22,9 +22,10 @@ tauri-build = { version = "2.0.0-beta", features = [] } serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } tauri = { version = "2.0.0-beta", features = [] } +color-eyre = "0.6.2" [features] # this feature is used for production builds or when `devPath` points to the filesystem and the built-in dev server is disabled. # If you use cargo directly instead of tauri's cli you can use this feature flag to switch between tauri's `dev` and `build` modes. # DO NOT REMOVE!! -custom-protocol = [ "tauri/custom-protocol" ] +custom-protocol = ["tauri/custom-protocol"] diff --git a/front/tauri/capabilities/migrated.json b/front/tauri/capabilities/migrated.json index 0fff22326..4abe92575 100644 --- a/front/tauri/capabilities/migrated.json +++ b/front/tauri/capabilities/migrated.json @@ -2,7 +2,7 @@ "identifier": "migrated", "description": "permissions that were migrated from v1", "context": "local", - "windows": ["main"], + "windows": ["main", "oauth"], "permissions": [ "path:default", "event:default", diff --git a/front/tauri/capabilities/oauth.json b/front/tauri/capabilities/oauth.json new file mode 100644 index 000000000..ac8ea9e66 --- /dev/null +++ b/front/tauri/capabilities/oauth.json @@ -0,0 +1,8 @@ +{ + "identifier": "oauth2", + "description": "permissions that required for OAuth2 login window", + "context": "local", + "windows": ["main"], + "permissions": ["webview:allow-create-webview-window", "window:allow-close"], + "platforms": ["linux", "macOS", "windows", "android", "iOS"] +} diff --git a/front/tauri/rust-toolchain.toml b/front/tauri/rust-toolchain.toml new file mode 100644 index 000000000..92ebe6655 --- /dev/null +++ b/front/tauri/rust-toolchain.toml @@ -0,0 +1,4 @@ +[toolchain] +profile = "minimal" +channel = "1.71.0" +components = ["rust-src", "rust-analyzer", "clippy"] diff --git a/front/tauri/src/main.rs b/front/tauri/src/main.rs index a406808a5..41ef8437d 100644 --- a/front/tauri/src/main.rs +++ b/front/tauri/src/main.rs @@ -1,6 +1,12 @@ // Prevents additional console window on Windows in release, DO NOT REMOVE!! #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] -fn main() { +use color_eyre::Result; + +fn main() -> Result<()> { + color_eyre::install()?; + funkwhale_lib::run(); + + Ok(()) } diff --git a/front/tauri/tauri.conf.json b/front/tauri/tauri.conf.json index ef3861aa1..a999007af 100644 --- a/front/tauri/tauri.conf.json +++ b/front/tauri/tauri.conf.json @@ -10,11 +10,12 @@ }, "windows": [ { + "label": "main", "fullscreen": false, "height": 600, + "width": 800, "resizable": true, - "title": "Funkwhale", - "width": 800 + "title": "Funkwhale" } ] },