Skip to content

Commit e2d83e4

Browse files
ProspectorIMB11
andauthored
deeplink sign in for app, give it its own session token (#7791)
* deeplink sign in flow * prepr * fix: conflict * emit warnings on deeplink fail --------- Co-authored-by: Calum H. (IMB11) <contact@cal.engineer>
1 parent 3ccb62b commit e2d83e4

46 files changed

Lines changed: 1595 additions & 444 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎Cargo.lock‎

Lines changed: 109 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎Cargo.toml‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ actix-rt = "2.11.0"
3333
actix-web = "4.11.0"
3434
actix-web-prom = "0.10.0"
3535
actix-ws = "0.3.0"
36+
aes-gcm = "0.10.3"
3637
arc-swap = "1.7.1"
3738
argon2 = { version = "0.5.3", features = ["std"] }
3839
ariadne = { path = "packages/ariadne" }
@@ -96,6 +97,7 @@ governor = "0.10.4"
9697
heck = "0.5.0"
9798
hex = "0.4.3"
9899
hickory-resolver = "0.25.2"
100+
hkdf = "0.12.4"
99101
hmac = "0.12.1"
100102
httpdate = "1.0.3"
101103
hyper = "1.7.0"

‎apps/app/Cargo.toml‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,23 @@ repository = "https://github.com/modrinth/code/apps/app/"
88
license = "GPL-3.0-only"
99

1010
[dependencies]
11+
aes-gcm = { workspace = true }
1112
async_zip = { workspace = true, features = ["deflate", "tokio-fs"] }
13+
base64 = { workspace = true }
1214
chrono = { workspace = true }
1315
daedalus = { workspace = true }
1416
dashmap = { workspace = true }
1517
either = { workspace = true }
1618
enumset = { workspace = true, features = ["serde"] }
19+
hkdf = { workspace = true }
1720
hyper = { workspace = true, features = ["server"] }
1821
hyper-util = { workspace = true }
1922
image = { workspace = true, features = ["gif", "jpeg", "png", "webp"] }
2023
native-dialog = { workspace = true }
24+
p256 = { workspace = true, features = ["ecdh"] }
2125
paste = { workspace = true }
2226
path-util = { workspace = true }
27+
rand = { workspace = true }
2328
serde = { workspace = true, features = ["derive"] }
2429
serde_json = { workspace = true }
2530
serde_with = { workspace = true }

‎apps/app/src/api/mr_auth.rs‎

Lines changed: 47 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
use crate::api::Result;
22
use crate::api::TheseusSerializableError;
33
use crate::api::oauth_utils;
4+
use crate::api::oauth_utils::auth_code_reply::AuthReplySession;
5+
use crate::api::oauth_utils::auth_seal::AuthSeal;
46
use tauri::Manager;
57
use tauri::Runtime;
68
use tauri::plugin::TauriPlugin;
79
use tauri_plugin_opener::OpenerExt;
810
use theseus::prelude::*;
9-
use tokio::sync::oneshot;
11+
use url::Url;
12+
13+
const LAUNCHER_AUTH_PROTOCOL: &str = "2";
1014

1115
pub fn init<R: tauri::Runtime>() -> TauriPlugin<R> {
1216
tauri::plugin::Builder::new("mr-auth")
@@ -28,27 +32,16 @@ pub async fn modrinth_login<R: Runtime>(
2832
flow: mr_auth::ModrinthAuthFlow,
2933
add_account: Option<bool>,
3034
) -> Result<ModrinthCredentials> {
31-
let (auth_code_recv_socket_tx, auth_code_recv_socket) = oneshot::channel();
32-
let auth_code = tokio::spawn(oauth_utils::auth_code_reply::listen(
33-
auth_code_recv_socket_tx,
34-
));
35-
36-
let auth_code_recv_socket = auth_code_recv_socket.await.unwrap()?;
37-
38-
let mut auth_request_uri = format!(
39-
"{}?launcher=true&ipver={}&port={}",
35+
let seal = AuthSeal::generate();
36+
let mut reply = AuthReplySession::start(seal.nonce().to_string());
37+
let auth_code_recv_socket = reply.socket_addr().await?;
38+
let auth_request_uri = auth_request_url(
4039
mr_auth::authenticate_begin_flow(flow).await?,
41-
if auth_code_recv_socket.is_ipv4() {
42-
"4"
43-
} else {
44-
"6"
45-
},
46-
auth_code_recv_socket.port()
47-
);
48-
49-
if add_account.unwrap_or(false) {
50-
auth_request_uri.push_str("&add_account=true");
51-
}
40+
&auth_code_recv_socket,
41+
&seal.public_key()?,
42+
seal.nonce(),
43+
add_account.unwrap_or(false),
44+
)?;
5245

5346
app.opener()
5447
.open_url(auth_request_uri, None::<&str>)
@@ -61,13 +54,13 @@ pub async fn modrinth_login<R: Runtime>(
6154
)
6255
})?;
6356

64-
let Some(auth_code) = auth_code.await.unwrap()? else {
57+
let Some(auth_code) = reply.wait().await? else {
6558
return Err(TheseusSerializableError::Theseus(
6659
theseus::ErrorKind::OtherError("Login canceled".into()).into(),
6760
));
6861
};
69-
70-
let credentials = mr_auth::authenticate_finish_flow(&auth_code).await?;
62+
let session_token = seal.open(&auth_code)?;
63+
let credentials = mr_auth::authenticate_finish_flow(&session_token).await?;
7164

7265
if let Some(main_window) = app.get_window("main") {
7366
main_window.set_focus().ok();
@@ -103,5 +96,34 @@ pub async fn remove_account(user_id: String) -> Result<()> {
10396

10497
#[tauri::command]
10598
pub fn cancel_modrinth_login() {
106-
oauth_utils::auth_code_reply::stop_listeners();
99+
oauth_utils::auth_code_reply::cancel_pending_auth();
100+
}
101+
102+
fn auth_request_url(
103+
begin_url: &str,
104+
socket: &std::net::SocketAddr,
105+
public_key: &str,
106+
nonce: &str,
107+
add_account: bool,
108+
) -> Result<String> {
109+
let mut url = Url::parse(begin_url).map_err(|error| -> theseus::Error {
110+
theseus::ErrorKind::OtherError(format!(
111+
"Failed to build auth request URI: {error}"
112+
))
113+
.into()
114+
})?;
115+
{
116+
let mut query = url.query_pairs_mut();
117+
query.append_pair("launcher", "true");
118+
query.append_pair("protocol", LAUNCHER_AUTH_PROTOCOL);
119+
query.append_pair("key", public_key);
120+
query.append_pair("nonce", nonce);
121+
query.append_pair("ipver", if socket.is_ipv4() { "4" } else { "6" });
122+
query.append_pair("port", &socket.port().to_string());
123+
if add_account {
124+
query.append_pair("add_account", "true");
125+
}
126+
}
127+
128+
Ok(url.into())
107129
}

0 commit comments

Comments
 (0)