From c30bfdb2eb118a36034313b84b5a6dec5d97ffc3 Mon Sep 17 00:00:00 2001 From: newt Date: Wed, 27 Nov 2024 00:22:53 +0000 Subject: [PATCH] feat: authentication --- .vscode/settings.json | 3 +- justfile | 2 +- package.json | 1 + pnpm-lock.yaml | 10 + src-tauri/Cargo.lock | 352 +++++++++++++++++++++++++++- src-tauri/Cargo.toml | 3 +- src-tauri/capabilities/default.json | 9 +- src-tauri/src/commands.rs | 1 + src-tauri/src/commands/auth.rs | 89 +++++++ src-tauri/src/data.rs | 5 + src-tauri/src/error.rs | 14 +- src-tauri/src/lib.rs | 39 +-- src-tauri/src/prelude.rs | 2 + src/routes/+page.svelte | 21 +- 14 files changed, 501 insertions(+), 50 deletions(-) create mode 100644 src-tauri/src/commands.rs create mode 100644 src-tauri/src/commands/auth.rs create mode 100644 src-tauri/src/prelude.rs diff --git a/.vscode/settings.json b/.vscode/settings.json index 6bd9aac..7e01eb1 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,7 +4,8 @@ "node_modules": true, ".svelte-kit": true, "**/target": true, - "build": true + "build": true, + "src-tauri/gen": true }, "rust-analyzer.linkedProjects": [ "src-tauri/Cargo.toml" diff --git a/justfile b/justfile index 0244953..5a5a8f3 100644 --- a/justfile +++ b/justfile @@ -14,4 +14,4 @@ cargo +cmd: [doc('Run tauri command')] tauri +cmd: - pnpm run tauri {cmd} + pnpm run tauri {{cmd}} diff --git a/package.json b/package.json index a672b86..3762436 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "license": "MIT", "dependencies": { "@tauri-apps/api": "^2", + "@tauri-apps/plugin-http": "~2", "@tauri-apps/plugin-shell": "^2" }, "devDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 903cc8a..046ad3a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,6 +11,9 @@ importers: '@tauri-apps/api': specifier: ^2 version: 2.1.1 + '@tauri-apps/plugin-http': + specifier: ~2 + version: 2.0.1 '@tauri-apps/plugin-shell': specifier: ^2 version: 2.0.1 @@ -439,6 +442,9 @@ packages: engines: {node: '>= 10'} hasBin: true + '@tauri-apps/plugin-http@2.0.1': + resolution: {integrity: sha512-j6IA3pVBybSCwPpsihpX4z8bs6PluuGtr06ahL/xy4D8HunNBTmRmadJrFOQi0gOAbaig4MkQ15nzNLBLy8R1A==} + '@tauri-apps/plugin-shell@2.0.1': resolution: {integrity: sha512-akU1b77sw3qHiynrK0s930y8zKmcdrSD60htjH+mFZqv5WaakZA/XxHR3/sF1nNv9Mgmt/Shls37HwnOr00aSw==} @@ -1318,6 +1324,10 @@ snapshots: '@tauri-apps/cli-win32-ia32-msvc': 2.1.0 '@tauri-apps/cli-win32-x64-msvc': 2.1.0 + '@tauri-apps/plugin-http@2.0.1': + dependencies: + '@tauri-apps/api': 2.1.1 + '@tauri-apps/plugin-shell@2.0.1': dependencies: '@tauri-apps/api': 2.1.1 diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 8258070..ea3e2b6 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -85,6 +85,12 @@ dependencies = [ "system-deps", ] +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "autocfg" version = "1.4.0" @@ -342,7 +348,7 @@ dependencies = [ "bitflags 2.6.0", "block", "cocoa-foundation", - "core-foundation", + "core-foundation 0.10.0", "core-graphics", "foreign-types", "libc", @@ -357,7 +363,7 @@ checksum = "e14045fb83be07b5acf1c0884b2180461635b433455fa35d1cd6f17f1450679d" dependencies = [ "bitflags 2.6.0", "block", - "core-foundation", + "core-foundation 0.10.0", "core-graphics-types", "libc", "objc", @@ -408,6 +414,16 @@ dependencies = [ "url", ] +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation" version = "0.10.0" @@ -431,7 +447,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1" dependencies = [ "bitflags 2.6.0", - "core-foundation", + "core-foundation 0.10.0", "core-graphics-types", "foreign-types", "libc", @@ -444,7 +460,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" dependencies = [ "bitflags 2.6.0", - "core-foundation", + "core-foundation 0.10.0", "libc", ] @@ -563,6 +579,12 @@ dependencies = [ "syn 2.0.89", ] +[[package]] +name = "data-url" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c297a1c74b71ae29df00c3e22dd9534821d60eb9af5a0192823fa2acea70c2a" + [[package]] name = "deranged" version = "0.3.11" @@ -707,14 +729,15 @@ name = "echoed" version = "0.1.0" dependencies = [ "regex", - "reqwest", "reqwest_cookie_store", "serde", "serde_json", "tauri", "tauri-build", + "tauri-plugin-http", "tauri-plugin-shell", "thiserror 2.0.3", + "url", ] [[package]] @@ -1068,8 +1091,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi 0.11.0+wasi-snapshot-preview1", + "wasm-bindgen", ] [[package]] @@ -1226,6 +1251,25 @@ dependencies = [ "syn 2.0.89", ] +[[package]] +name = "h2" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccae279728d634d083c00f6099cb58f01cc99c145b84b8be2f6c74618d79922e" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap 2.6.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -1325,6 +1369,7 @@ dependencies = [ "bytes", "futures-channel", "futures-util", + "h2", "http", "http-body", "httparse", @@ -1335,6 +1380,24 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-rustls" +version = "0.27.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" +dependencies = [ + "futures-util", + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", + "webpki-roots", +] + [[package]] name = "hyper-util" version = "0.1.10" @@ -2591,6 +2654,58 @@ dependencies = [ "memchr", ] +[[package]] +name = "quinn" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62e96808277ec6f97351a2380e6c25114bc9e67037775464979f3037c92d05ef" +dependencies = [ + "bytes", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror 2.0.3", + "tokio", + "tracing", +] + +[[package]] +name = "quinn-proto" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2fe5ef3495d7d2e377ff17b1a8ce2ee2ec2a18cde8b6ad6619d65d0701c135d" +dependencies = [ + "bytes", + "getrandom 0.2.15", + "rand 0.8.5", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.3", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5a626c6807713b15cac82a6acaccd6043c9a5408c24baae07611fec3f243da" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.59.0", +] + [[package]] name = "quote" version = "1.0.37" @@ -2746,12 +2861,15 @@ dependencies = [ "bytes", "cookie", "cookie_store", + "encoding_rs", "futures-core", "futures-util", + "h2", "http", "http-body", "http-body-util", "hyper", + "hyper-rustls", "hyper-util", "ipnet", "js-sys", @@ -2761,11 +2879,17 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", + "quinn", + "rustls", + "rustls-pemfile", + "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", "sync_wrapper", + "system-configuration", "tokio", + "tokio-rustls", "tokio-util", "tower-service", "url", @@ -2773,6 +2897,7 @@ dependencies = [ "wasm-bindgen-futures", "wasm-streams", "web-sys", + "webpki-roots", "windows-registry", ] @@ -2788,12 +2913,33 @@ dependencies = [ "url", ] +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.15", + "libc", + "spin", + "untrusted", + "windows-sys 0.52.0", +] + [[package]] name = "rustc-demangle" version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +[[package]] +name = "rustc-hash" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" + [[package]] name = "rustc_version" version = "0.4.1" @@ -2803,6 +2949,49 @@ dependencies = [ "semver", ] +[[package]] +name = "rustls" +version = "0.23.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9cc1d47e243d655ace55ed38201c19ae02c148ae56412ab8750e8f0166ab7f" +dependencies = [ + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b" +dependencies = [ + "web-time", +] + +[[package]] +name = "rustls-webpki" +version = "0.102.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + [[package]] name = "ryu" version = "1.0.18" @@ -3140,6 +3329,12 @@ dependencies = [ "system-deps", ] +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -3178,6 +3373,12 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "swift-rs" version = "1.0.7" @@ -3231,6 +3432,27 @@ dependencies = [ "syn 2.0.89", ] +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags 2.6.0", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "system-deps" version = "6.2.2" @@ -3252,7 +3474,7 @@ checksum = "6682a07cf5bab0b8a2bd20d0a542917ab928b5edb75ebd4eda6b05cbaab872da" dependencies = [ "bitflags 2.6.0", "cocoa", - "core-foundation", + "core-foundation 0.10.0", "core-graphics", "crossbeam-channel", "dispatch", @@ -3430,6 +3652,49 @@ dependencies = [ "walkdir", ] +[[package]] +name = "tauri-plugin-fs" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96ba7d46e86db8c830d143ef90ab5a453328365b0cc834c24edea4267b16aba0" +dependencies = [ + "anyhow", + "dunce", + "glob", + "percent-encoding", + "schemars", + "serde", + "serde_json", + "serde_repr", + "tauri", + "tauri-plugin", + "thiserror 1.0.69", + "url", + "uuid", +] + +[[package]] +name = "tauri-plugin-http" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c752aee1b00ec3c4d4f440095995d9bd2c640b478f2067d1fba388900b82eb96" +dependencies = [ + "data-url", + "http", + "regex", + "reqwest", + "schemars", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "tauri-plugin-fs", + "thiserror 1.0.69", + "tokio", + "url", + "urlpattern", +] + [[package]] name = "tauri-plugin-shell" version = "2.0.2" @@ -3641,6 +3906,21 @@ dependencies = [ "zerovec", ] +[[package]] +name = "tinyvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "tokio" version = "1.41.1" @@ -3653,9 +3933,32 @@ dependencies = [ "mio", "pin-project-lite", "socket2", + "tokio-macros", "windows-sys 0.52.0", ] +[[package]] +name = "tokio-macros" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.89", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" +dependencies = [ + "rustls", + "rustls-pki-types", + "tokio", +] + [[package]] name = "tokio-util" version = "0.7.12" @@ -3852,10 +4155,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" [[package]] -name = "url" -version = "2.5.3" +name = "untrusted" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d157f1b96d14500ffdc1f10ba712e780825526c03d9a49b4d0324b0d9113ada" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" dependencies = [ "form_urlencoded", "idna", @@ -4056,6 +4365,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "webkit2gtk" version = "2.0.1" @@ -4100,6 +4419,15 @@ dependencies = [ "system-deps", ] +[[package]] +name = "webpki-roots" +version = "0.26.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d642ff16b7e79272ae451b7322067cdc17cadf68c23264be9d94a32319efe7e" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "webview2-com" version = "0.33.0" @@ -4649,6 +4977,12 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + [[package]] name = "zerovec" version = "0.10.4" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 6da73ad..7233fef 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -18,10 +18,11 @@ tauri = { version = "2", features = [] } tauri-plugin-shell = "2" serde = { version = "1", features = ["derive"] } serde_json = "1" -reqwest = { version = "0.12.9", default-features = false, features = ["json", "multipart"] } reqwest_cookie_store = { path = "../reqwest_cookie_store_tokio" } thiserror = "2.0.3" regex = { version = "1.11.1", default-features = false, features = ["perf", "std"] } +url = { version = "2.5.4", default-features = false } +tauri-plugin-http = { version = "2", features = ["multipart", "json"] } [profile.dev] incremental = true # Compile your binary in smaller steps. diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json index 3bb4cc4..318b5e2 100644 --- a/src-tauri/capabilities/default.json +++ b/src-tauri/capabilities/default.json @@ -2,9 +2,12 @@ "$schema": "../gen/schemas/desktop-schema.json", "identifier": "default", "description": "Capability for the main window", - "windows": ["main"], + "windows": [ + "main" + ], "permissions": [ "core:default", - "shell:allow-open" + "shell:allow-open", + "http:default" ] -} +} \ No newline at end of file diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs new file mode 100644 index 0000000..0e4a05d --- /dev/null +++ b/src-tauri/src/commands.rs @@ -0,0 +1 @@ +pub mod auth; diff --git a/src-tauri/src/commands/auth.rs b/src-tauri/src/commands/auth.rs new file mode 100644 index 0000000..edc56e2 --- /dev/null +++ b/src-tauri/src/commands/auth.rs @@ -0,0 +1,89 @@ +use crate::prelude::*; +use tauri_plugin_http::reqwest::multipart::Form; +use url::Url; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("Failed to find {0}.")] + Find(&'static str), + + #[error("Invalid details.")] + InvalidDetails, + + #[error("Invalid URL.")] + InvalidUrl, +} + +/// Authenticate with echo360 +#[command] +pub async fn authenticate( + email: Cow<'static, str>, + password: Cow<'static, str>, + url: Cow<'static, str>, + state: State<'_>, +) -> Result<()> { + let tauri_state = state; + let state = state.lock().await; + let parsed_url = Url::parse(&url)?; + let domain = parsed_url.domain().ok_or(Error::InvalidUrl)?; + + // get csrf token + state.client.get(url.to_string()).send().await?; + + let csrf_token = state + .jar + .lock() + .await + .get(domain, "/", "PLAY_SESSION") + .map(|cookie| { + let value = cookie.value().to_string(); + value + .split('&') + .nth(1) + .map(|part| part.split('=').last()) + .flatten() + .map(|x| x.to_string()) + }) + .flatten() + .ok_or(Error::Find("CSRF token"))?; + + // authenticate + state + .client + .post(format!("{}/login", url)) + .query(&[("csrfToken", csrf_token)]) + .multipart(Form::new().text("email", email).text("password", password)) + .send() + .await?; + + // check if authentication was successful + drop(state); + if is_authenticated(url, tauri_state).await? { + Ok(()) + } else { + Err(Error::InvalidDetails.into()) + } +} + +/// Check if the user is authenticated with echo360 +#[command] +pub async fn is_authenticated(url: Cow<'static, str>, state: State<'_>) -> Result { + let state = state.lock().await; + + // fetch courses page + let html = state + .client + .get(format!("{}/courses", url)) + .send() + .await? + .text() + .await?; + + // the courses page redirects to a sign in page if the user is not authenticated + // if we can find the user's first name somewhere in the html, then the user must be authenticated + let authenticated = regex::Regex::new(r#"\\"firstName\\":\\"(\w+)\\""#)? + .captures(&html) + .is_some(); + + Ok(authenticated) +} diff --git a/src-tauri/src/data.rs b/src-tauri/src/data.rs index 187c887..992b33e 100644 --- a/src-tauri/src/data.rs +++ b/src-tauri/src/data.rs @@ -1,10 +1,15 @@ +use reqwest_cookie_store::CookieStoreMutex; +use std::sync::Arc; use tauri::async_runtime::Mutex; +use tauri_plugin_http::reqwest; // todo: reqwest client for each profile, or a way to swap the cookie jar used by a reqwest client /// Tauri application state pub struct AppState { /// Reqwest client connected to profile cookie jar pub client: reqwest::Client, + /// Cookie jar attached to the reqwest client + pub jar: Arc, } /// Wrapper around `tauri::State` to use custom app state. diff --git a/src-tauri/src/error.rs b/src-tauri/src/error.rs index b9ddcb2..11623bf 100644 --- a/src-tauri/src/error.rs +++ b/src-tauri/src/error.rs @@ -1,13 +1,23 @@ +use crate::commands::*; + pub type Result = std::result::Result; #[derive(Debug, thiserror::Error)] pub enum Error { #[error(transparent)] Tauri(#[from] tauri::Error), + #[error(transparent)] - Reqwest(#[from] reqwest::Error), - #[error(transparent)] + Http(#[from] tauri_plugin_http::reqwest::Error), + + #[error("Failed to parse regex.")] Regex(#[from] regex::Error), + + #[error("Failed to parse url.")] + Url(#[from] url::ParseError), + + #[error(transparent)] + Auth(#[from] auth::Error), } impl serde::Serialize for Error { diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 6bbfa8b..a3af99f 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -1,46 +1,23 @@ -use data::{AppState, State}; -use error::Result; +use commands::auth::*; +use data::AppState; use reqwest_cookie_store::CookieStoreMutex; use std::sync::Arc; use tauri::{async_runtime::Mutex, Manager}; +mod commands; mod data; mod error; +mod prelude; #[macro_use] extern crate tauri; -const DEFAULT_ECHO360: &str = "https://echo360.org.uk"; - -/// Check if the user is authenticated with echo360 -#[command(rename_all = "snake_case")] -async fn is_authenticated(state: State<'_>) -> Result { - // get client - let state = state.lock().await; - - // fetch courses page - let html = state - .client - .get(format!("{}/courses", DEFAULT_ECHO360)) - .send() - .await? - .text() - .await?; - - // the courses page redirects to a sign in page if the user is not authenticated - // if we can find the user's first name somewhere in the html, then the user must be authenticated - let authenticated = regex::Regex::new(r#"\\"firstName\\":\\"(\w+)\\""#)? - .captures(&html) - .is_some(); - - Ok(authenticated) -} - #[cfg_attr(mobile, mobile_entry_point)] pub fn run() { tauri::Builder::default() + .plugin(tauri_plugin_http::init()) .plugin(tauri_plugin_shell::init()) - .invoke_handler(tauri::generate_handler![is_authenticated]) + .invoke_handler(tauri::generate_handler![authenticate, is_authenticated]) .setup(|app| { // todo: load profile file, or create if it does not exist @@ -48,12 +25,12 @@ pub fn run() { // todo: load cookies into cookie jar //? https://docs.rs/tauri/1.8.1/tauri/api/path/fn.app_config_dir.html let jar = Arc::new(CookieStoreMutex::default()); - let client = reqwest::Client::builder() + let client = tauri_plugin_http::reqwest::Client::builder() .cookie_provider(jar.clone()) .build()?; // store app state - app.manage(Mutex::new(AppState { client })); + app.manage(Mutex::new(AppState { client, jar })); Ok(()) }) diff --git a/src-tauri/src/prelude.rs b/src-tauri/src/prelude.rs new file mode 100644 index 0000000..63cb418 --- /dev/null +++ b/src-tauri/src/prelude.rs @@ -0,0 +1,2 @@ +pub use crate::{data::State, error::Result}; +pub use std::borrow::Cow; diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 1065d78..e8bdee7 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -1,7 +1,20 @@ {#await is_authenticated} @@ -10,7 +23,11 @@ {#if authenticated}

Authenticated

{:else} -

Unauthenticated

+
+ + + +
{/if} {/await}