Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Authentication #37

Merged
merged 7 commits into from
Jul 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,198 changes: 568 additions & 630 deletions Cargo.lock

Large diffs are not rendered by default.

20 changes: 5 additions & 15 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,7 @@
gtk4 gdk-pixbuf atk webkitgtk dbus
];
shell = [
toolchain
(with fenix; combine [toolchain default.clippy rust-analyzer])
(with fenix; combine [toolchain default.clippy complete.rust-src rust-analyzer])
git
jdk17 jdk8
];
Expand All @@ -58,19 +57,10 @@
cli = utils.mkApp {
drv = self.packages.${system}.theseus-cli;
};
cli-test = utils.mkApp {
drv = pkgs.writeShellApplication {
name = "theseus-test-cli";
runtimeInputs = [
(self.packages.${system}.theseus-cli.overrideAttrs (old: old // {
release = false;
}))
];
text = ''
DUMMY_ID="$(printf '%0.sa' {1..32})"
theseus_cli profile run -t "" -n "Test" -i "$DUMMY_ID" "$@"
'';
};
cli-dev = utils.mkApp {
drv = self.packages.${system}.theseus-cli.overrideAttrs (old: old // {
release = false;
});
};
};

Expand Down
8 changes: 0 additions & 8 deletions shell.nix

This file was deleted.

40 changes: 17 additions & 23 deletions theseus/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,38 +7,32 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
thiserror = "1.0"
async-trait = "0.1.51"

daedalus = { version = "0.1.16", features = ["bincode"] }

bytes = "1"
bincode = { version = "2.0.0-rc.1", features = ["serde"] }
sled = { version = "0.34.7", features = ["compression"] }

reqwest = { version = "0.11", features = ["json"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
json5 = "0.4.1"
chrono = { version = "0.4", features = ["serde"] }
uuid = { version = "0.8", features = ["serde", "v4"] }
bytes = "1"
zip = "0.5"
zip-extensions = "0.6"
sha1 = { version = "0.6.0", features = ["std"]}
path-clean = "0.1.0"
fs_extra = "1.2.0"
dirs = "4.0"
sled = { version = "0.34.7", features = ["compression"] }
url = "2.2"
uuid = { version = "1.1", features = ["serde", "v4"] }
zip = "0.5"

chrono = { version = "0.4.19", features = ["serde"] }
daedalus = { version = "0.1.16", features = ["bincode"] }
dirs = "4.0"
# TODO: possibly replace with tracing to have structured logging
log = "0.4.14"
regex = "1.5"

tokio = { version = "1", features = ["full"] }
futures = "0.3"

sys-info = "0.9.0"
thiserror = "1.0"
tracing = "0.1"
tracing-error = "0.2"

log = "0.4.14"
const_format = "0.2.22"
async-tungstenite = { version = "0.17", features = ["tokio-runtime", "tokio-native-tls"] }
futures = "0.3"
once_cell = "1.9.0"
reqwest = { version = "0.11", features = ["json"] }
tokio = { version = "1", features = ["full"] }

[dev-dependencies]
argh = "0.1.6"
Expand Down
100 changes: 100 additions & 0 deletions theseus/src/api/auth.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
//! Authentication flow interface
use crate::{launcher::auth as inner, State};
use futures::prelude::*;
use tokio::sync::oneshot;

pub use inner::Credentials;

/// Authenticate a user with Hydra
/// To run this, you need to first spawn this function as a task, then
/// open a browser to the given URL and finally wait on the spawned future
/// with the ability to cancel in case the browser is closed before finishing
#[tracing::instrument]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are these macros needed everywhere with this lib?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only on functions which need to be known about in the spantrace. Unfortunately, since async Rust doesn't have any real consistent stack to trace usefully, we need to use the macro to determine where we are in actual control flow.

pub async fn authenticate(
browser_url: oneshot::Sender<url::Url>,
) -> crate::Result<Credentials> {
let mut flow = inner::HydraAuthFlow::new().await?;
let state = State::get().await?;
let mut users = state.users.write().await;

let url = flow.prepare_login_url().await?;
browser_url.send(url).map_err(|url| {
crate::ErrorKind::OtherError(format!(
"Error sending browser url to parent: {url}"
))
})?;

let credentials = flow.extract_credentials().await?;
users.insert(&credentials)?;

if state.settings.read().await.default_user.is_none() {
let mut settings = state.settings.write().await;
settings.default_user = Some(credentials.id);
}

Ok(credentials)
}

/// Refresh some credentials using Hydra, if needed
#[tracing::instrument]
pub async fn refresh(
user: uuid::Uuid,
update_name: bool,
) -> crate::Result<Credentials> {
let state = State::get().await?;
let mut users = state.users.write().await;

futures::future::ready(users.get(user)?.ok_or_else(|| {
crate::ErrorKind::OtherError(format!(
"Tried to refresh nonexistent user with ID {user}"
))
.as_error()
}))
.and_then(|mut credentials| async move {
if chrono::offset::Utc::now() > credentials.expires {
inner::refresh_credentials(&mut credentials).await?;
if update_name {
inner::refresh_username(&mut credentials).await?;
}
}
users.insert(&credentials)?;
Ok(credentials)
})
.await
}

/// 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
.first()?
.map(|it| uuid::Uuid::from_slice(&it.0))
.transpose()?;
}

users.remove(user)?;
Ok(())
}

/// Check if a user exists in Theseus
#[tracing::instrument]
pub async fn has_user(user: uuid::Uuid) -> crate::Result<bool> {
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<Box<[Credentials]>> {
let state = State::get().await?;
let users = state.users.read().await;
users.iter().collect()
}
11 changes: 5 additions & 6 deletions theseus/src/api/mod.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
//! API for interacting with Theseus
pub mod auth;
pub mod profile;

pub mod data {
pub use crate::{
launcher::Credentials,
state::{
DirectoryInfo, Hooks, JavaSettings, MemorySettings, ModLoader,
ProfileMetadata, Settings, WindowSize,
},
pub use crate::state::{
DirectoryInfo, Hooks, JavaSettings, MemorySettings, ModLoader,
ProfileMetadata, Settings, WindowSize,
};
}

pub mod prelude {
pub use crate::{
auth::{self, Credentials},
data::*,
profile::{self, Profile},
State,
Expand Down
Loading