changes saved in my editor
This commit is contained in:
697
parts/auth/src/client.rs
Normal file
697
parts/auth/src/client.rs
Normal file
@@ -0,0 +1,697 @@
|
||||
use chrono::{DateTime, TimeDelta, Utc};
|
||||
use mize::config::OptVal;
|
||||
use mize::config::thread;
|
||||
use mize::include_native;
|
||||
use mize::prelude::*;
|
||||
use mize::out;
|
||||
use mize::{
|
||||
item::ItemData, mize_err, mize_part, Mize, MizeError, MizePart, MizePartCreate, MizeResult,
|
||||
};
|
||||
use openidconnect::{
|
||||
ClientId, DeviceAuthorizationResponse, EmptyExtraDeviceAuthorizationFields, IssuerUrl,
|
||||
OAuth2TokenResponse, RefreshToken, Scope, TokenResponse,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::Write;
|
||||
use std::{path::PathBuf, thread};
|
||||
use tracing::{info, warn};
|
||||
use mize_part_cli::CliPartState;
|
||||
|
||||
// console_log macro
|
||||
// that can be copied into other files for debugging purposes
|
||||
#[cfg(feature = "target-obsidian")]
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[cfg(feature = "target-obsidian")]
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
#[wasm_bindgen(js_namespace = console)]
|
||||
fn log(s: &str);
|
||||
}
|
||||
|
||||
#[cfg(feature = "target-obsidian")]
|
||||
macro_rules! console_log {
|
||||
// Note that this is using the `log` function imported above during
|
||||
// `bare_bones`
|
||||
($($t:tt)*) => (unsafe { log(&format_args!($($t)*).to_string())})
|
||||
}
|
||||
|
||||
#[cfg(feature = "target-os")]
|
||||
use mize_part_cli::clap::Command;
|
||||
|
||||
#[derive(Default)]
|
||||
#[mize_part]
|
||||
pub struct ClientAuth {
|
||||
mize: Mize,
|
||||
secrets: Option<ClientSecrets>,
|
||||
}
|
||||
|
||||
#[derive(Default, Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct ClientSecrets {
|
||||
id_token: String,
|
||||
access_token: String,
|
||||
refresh_token: String,
|
||||
expiry: DateTime<Utc>,
|
||||
}
|
||||
|
||||
impl MizePartCreate for ClientAuth {}
|
||||
|
||||
impl MizePart for ClientAuth {
|
||||
fn name(&self) -> &'static str {
|
||||
"client_auth"
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "target-js")]
|
||||
pub fn store_secrets_js(mize: &Mize, secrets: &ClientSecrets) -> MizeResult<()> {
|
||||
use web_sys::window;
|
||||
|
||||
// Serialize struct to JSON string
|
||||
let serialized = serde_json::to_string(&secrets)?;
|
||||
|
||||
// Get browser window object
|
||||
let window = window().ok_or_else(|| mize_err!("No global window found"))?;
|
||||
|
||||
// Access local storage
|
||||
let storage = window
|
||||
.local_storage().map_err(|_| mize_err!("couldn't get local_storage"))?
|
||||
.ok_or_else(|| mize_err!("LocalStorage is not available"))?;
|
||||
|
||||
// Store the JSON string
|
||||
storage.set_item("mize_client_secrets", &serialized).map_err(|_| mize_err!("couldn't set item in local_storage"))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
impl ClientAuth {
|
||||
pub fn secrets(&self) -> Option<&ClientSecrets> {
|
||||
self.secrets.as_ref()
|
||||
}
|
||||
|
||||
pub fn id_token(&self) -> MizeResult<&str> {
|
||||
if self.secrets.is_none() {
|
||||
return Err(mize_err!("Client not logged in"));
|
||||
}
|
||||
Ok(&self.secrets.as_ref().unwrap().id_token)
|
||||
}
|
||||
|
||||
pub fn access_token(&self) -> MizeResult<&str> {
|
||||
if self.secrets.is_none() {
|
||||
return Err(mize_err!("Client not logged in"));
|
||||
}
|
||||
Ok(&self.secrets.as_ref().unwrap().access_token)
|
||||
}
|
||||
|
||||
pub fn refresh_token(&self) -> MizeResult<&str> {
|
||||
if self.secrets.is_none() {
|
||||
return Err(mize_err!("Client not logged in"));
|
||||
}
|
||||
Ok(&self.secrets.as_ref().unwrap().refresh_token)
|
||||
}
|
||||
|
||||
pub fn refresh(&mut self) -> MizeResult<()> {
|
||||
#[cfg(feature = "target-js")] {
|
||||
console_log!("refresh is not implemented yet on wasm");
|
||||
}
|
||||
|
||||
#[cfg(feature = "target-os")] {
|
||||
let secrets = self.secrets.clone().ok_or(mize_err!(
|
||||
"no client secrets, meaning client_auth.refresh() thread can't use the refresh token"
|
||||
))?;
|
||||
let new_secrets = self.mize.spawn_async_blocking(
|
||||
"auth.refresh_flow",
|
||||
refresh_flow(self.mize.clone(), secrets),
|
||||
)?;
|
||||
store_secrets(&mut self.mize, &new_secrets)?;
|
||||
self.secrets = Some(new_secrets);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn login(&mut self) -> MizeResult<()> {
|
||||
#[cfg(feature = "target-os")] {
|
||||
if self.secrets().is_none() {
|
||||
let secrets = self
|
||||
.mize
|
||||
.spawn_async_blocking("auth.login_flow", auth_flow(self.mize.clone()))?;
|
||||
store_secrets(&mut self.mize, &secrets)?;
|
||||
self.secrets = Some(secrets);
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "target-js")] {
|
||||
if self.secrets().is_none() {
|
||||
let mut mize_clone = self.mize.clone();
|
||||
wasm_bindgen_futures::spawn_local(async move {
|
||||
console_log!("before auth_flow");
|
||||
let secrets = match auth_flow(mize_clone.clone()).await {
|
||||
Ok(secrets) => secrets,
|
||||
Err(e) => {
|
||||
mize_clone.report_err(e);
|
||||
return ();
|
||||
}
|
||||
};
|
||||
console_log!("after auth_flow");
|
||||
if let Err(e) = store_secrets_js(&mut mize_clone, &secrets) {
|
||||
mize_clone.report_err(e);
|
||||
return ();
|
||||
}
|
||||
let mut auth = match mize_clone.get_part_native::<ClientAuth>("client_auth") {
|
||||
Ok(auth) => auth,
|
||||
Err(e) => {
|
||||
mize_clone.report_err(e);
|
||||
return ();
|
||||
}
|
||||
};
|
||||
auth.secrets = Some(secrets);
|
||||
});
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn part_auth() -> MizeResult<()> {
|
||||
|
||||
opt("cred")
|
||||
.fun(|| {
|
||||
let mize = get_global_mize()?;
|
||||
let secrets = do_login()?;
|
||||
ItemData::from_serializable(secrets)
|
||||
});
|
||||
|
||||
let mize = get_global_mize()?;
|
||||
|
||||
// start refresh tread if configured
|
||||
if mize
|
||||
.get_config("auth.client.refresh_thread")
|
||||
.unwrap_or(ItemData::new())
|
||||
.value_bool()
|
||||
.unwrap_or(false)
|
||||
{
|
||||
thread("auth.refresh_thread", start_refresh_thread());
|
||||
}
|
||||
|
||||
let cli = get_part_gen::<generated::CliPart>("cli");
|
||||
|
||||
|
||||
// cli command for os
|
||||
#[cfg(feature = "target-os")] {
|
||||
println!("hasdfaslkjfasldkfj");
|
||||
let cli_dyn = mize.get_part("cli")?;
|
||||
println!("hasdfaslkjfasldkfj three ");
|
||||
let mut cli = mize_part_cli::downcast_cli_part(cli_dyn)?;
|
||||
println!("hasdfaslkjfasldkfj two ");
|
||||
//let mut cli = mize.get_part_native::<CliPart>("cli")?;
|
||||
cli.set_command("ppc");
|
||||
cli.subcommand(Command::new("login"), move |_, mut mize| {
|
||||
let mut auth = mize.get_part_native::<ClientAuth>("client_auth")?;
|
||||
let secrets = if auth.secrets.is_some() {
|
||||
match mize.spawn_async_blocking(
|
||||
"client_auth",
|
||||
refresh_flow(mize.clone(), auth.secrets.clone().unwrap()),
|
||||
) {
|
||||
Ok(secrets) => secrets,
|
||||
Err(err) => {
|
||||
out!("err refreshing secrets: {:?}", err);
|
||||
mize.spawn_async_blocking("client_auth", auth_flow(mize_clone.clone()))?
|
||||
}
|
||||
}
|
||||
} else {
|
||||
mize.spawn_async_blocking("client_auth", auth_flow(mize_clone.clone()))?
|
||||
};
|
||||
store_secrets(&mut mize, &secrets)?;
|
||||
auth.secrets = Some(secrets);
|
||||
Ok(())
|
||||
})?;
|
||||
}
|
||||
|
||||
|
||||
// register command for obsidian
|
||||
#[cfg(feature = "target-obsidian")] {
|
||||
use crate::MartsCommand;
|
||||
console_log!("register obsidian command...");
|
||||
|
||||
let obsidian = mize.get_part_native::<crate::ObsidianPart>("obsidian")?;
|
||||
obsidian.add_command(MartsCommand {
|
||||
id: "mize-login".to_string(),
|
||||
name: "Mize Login".to_string(),
|
||||
callback: Box::new(|mut mize| {
|
||||
let mut auth = mize.get_part_native::<crate::ClientAuth>("client_auth")?;
|
||||
let res = auth.login();
|
||||
console_log!("auth.login done: {:?}", res);
|
||||
res
|
||||
}),
|
||||
})?;
|
||||
}
|
||||
|
||||
|
||||
auth_part.secrets = secrets;
|
||||
|
||||
mize.register_part(Box::new(auth_part))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn start_refresh_thread() -> MizeResult<()> {
|
||||
|
||||
// background thread for os targets
|
||||
#[cfg(feature = "target-os")] {
|
||||
let mut mize_clone = self.mize.clone();
|
||||
self.mize.spawn_background("auth.refresh", move || loop {
|
||||
info!("hi from auth.refresh thread...");
|
||||
thread::sleep(Duration::from_secs(33)); // TERRIBLE hack
|
||||
let auth = mize_clone.get_part_native::<ClientAuth>("client_auth")?;
|
||||
let expiry = auth.secrets.as_ref().unwrap().expiry.clone();
|
||||
info!("auth.refresh thread: now: {}", Utc::now());
|
||||
info!("auth.refresh thread: expiry: {}", expiry);
|
||||
let to_sleep = expiry - Utc::now() - TimeDelta::minutes(20);
|
||||
/*
|
||||
if to_sleep < TimeDelta::from_std(Duration::from_secs(0))? {
|
||||
if let Err(e) = auth.refresh() {
|
||||
e.log();
|
||||
}
|
||||
}
|
||||
*/
|
||||
drop(auth);
|
||||
|
||||
info!("auth.refresh: thread going to sleep for: {}", to_sleep);
|
||||
thread::sleep(to_sleep.to_std()?);
|
||||
|
||||
info!("refresh_thread: sleeping done");
|
||||
let mut auth = mize_clone.get_part_native::<ClientAuth>("auth")?;
|
||||
auth.refresh()?;
|
||||
drop(auth);
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/*
|
||||
// spawn an async background task doing the refresh
|
||||
#[cfg(feature = "target-js")] {
|
||||
use wasm_bindgen::prelude::*;
|
||||
use wasm_bindgen_futures::JsFuture;
|
||||
|
||||
// Get the window object
|
||||
let window = web_sys::window().expect("no global `window` exists");
|
||||
|
||||
// Clone mize for the closure
|
||||
let mize_clone = self.mize.clone();
|
||||
|
||||
// Create a closure that will be called periodically
|
||||
let refresh_closure = Closure::wrap(Box::new(move || {
|
||||
let mut mize_clone = mize_clone.clone();
|
||||
wasm_bindgen_futures::spawn_local(async move {
|
||||
let mut auth = mize_clone.get_part_native::<ClientAuth>("client_auth");
|
||||
match auth {
|
||||
Ok(mut auth_part) => {
|
||||
if let Some(secrets) = auth_part.secrets() {
|
||||
let now = Utc::now();
|
||||
let expiry = secrets.expiry;
|
||||
let time_until_expiry = expiry - now;
|
||||
|
||||
// Refresh if token will expire in less than 20 minutes
|
||||
if time_until_expiry < TimeDelta::minutes(20) {
|
||||
if let Err(e) = auth_part.refresh() {
|
||||
console_log!("Error refreshing token: {:?}", e);
|
||||
} else {
|
||||
console_log!("Token refreshed successfully");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
console_log!("Error getting auth part: {:?}", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}) as Box<dyn FnMut()>);
|
||||
|
||||
// Set up interval to run every 50 minutes (300000 ms)
|
||||
let interval_ms = 3000000;
|
||||
let _ = window.set_interval_with_callback_and_timeout_and_arguments_0(
|
||||
refresh_closure.as_ref().unchecked_ref(),
|
||||
interval_ms,
|
||||
);
|
||||
|
||||
// Leak the closure to prevent it from being dropped
|
||||
refresh_closure.forget();
|
||||
}
|
||||
*/
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn do_login() -> MizeResult<ClientSecrets> {
|
||||
let mut mize = get_global_mize()?;
|
||||
let mut secrets;
|
||||
if let Ok(val) = load_secrets(&mut mize) {
|
||||
if val.clone().unwrap().expiry < Utc::now() {
|
||||
// refresh if secrets are expired
|
||||
secrets = mize.spawn_async_blocking("auth.refresh", refresh_flow(mize, val))?;
|
||||
} else {
|
||||
secrets = val;
|
||||
}
|
||||
} else {
|
||||
secrets = mize.spawn_async_blocking("auth.login", auth_flow(mize))?;
|
||||
}
|
||||
Ok(secrets)
|
||||
}
|
||||
|
||||
fn load_secrets(mize: &mut Mize) -> MizeResult<ClientSecrets> {
|
||||
#[cfg(feature = "target-os")] {
|
||||
let mize_dir = mize.get_config("data_dir")?.value_string()?;
|
||||
let secrets_file_path = PathBuf::from(mize_dir).join("client_secrets.json");
|
||||
|
||||
let secrets_string = std::fs::read_to_string(secrets_file_path)?;
|
||||
let secrets: ClientSecrets = serde_json::from_str(&secrets_string)?;
|
||||
return Ok(secrets);
|
||||
}
|
||||
|
||||
#[cfg(feature = "target-js")] {
|
||||
use web_sys::window;
|
||||
|
||||
// Get browser window object
|
||||
let window = window().ok_or_else(|| mize_err!("No global window found"))?;
|
||||
|
||||
// Access local storage
|
||||
let storage = window
|
||||
.local_storage().map_err(|_| mize_err!("couldn't get local_storage"))?
|
||||
.ok_or_else(|| mize_err!("LocalStorage is not available"))?;
|
||||
|
||||
// Retrieve the string item
|
||||
return match storage.get_item("mize_client_secrets").map_err(|_| mize_err!("couldn't get item from local_storage"))? {
|
||||
Some(json_str) => {
|
||||
// Deserialize JSON string back into Client struct
|
||||
let secrets: ClientSecrets = serde_json::from_str(json_str.as_str())?;
|
||||
Ok(secrets)
|
||||
}
|
||||
None => Err(mize_err!("no secrets")), // Key does not exist
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fn store_secrets(mize: &mut Mize, secrets: &ClientSecrets) -> MizeResult<()> {
|
||||
|
||||
#[cfg(feature = "target-os")] {
|
||||
let mize_dir = mize.get_config("store")?.value_string()?;
|
||||
let secrets_file_path = PathBuf::from(mize_dir.clone())
|
||||
.join("misc")
|
||||
.join("client_secrets.json");
|
||||
std::fs::create_dir_all(PathBuf::from(mize_dir).join("misc"))?;
|
||||
|
||||
let secrets_string = serde_json::to_string(secrets)?;
|
||||
tracing::info!("secrets_file_path: {}", secrets_file_path.display());
|
||||
//std::fs::write(secrets_file_path, secrets_string)?;
|
||||
OpenOptions::new()
|
||||
.write(true)
|
||||
.create(true)
|
||||
.open(secrets_file_path)?
|
||||
.write_all(&secrets_string.as_bytes())?;
|
||||
|
||||
// update the spacetimedb_token in XDG_CONFIG_HOME/spacetime/cli.toml config file if configured to do so
|
||||
if mize
|
||||
.get_config("auth.client.update_spacetime_token")
|
||||
.unwrap_or(ItemData::empty())
|
||||
.as_bool()
|
||||
.unwrap_or(false)
|
||||
{
|
||||
update_spacetimedb_token(&secrets.id_token.as_str())?;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
use openidconnect::reqwest::Client as HttpClient;
|
||||
use openidconnect::DeviceAuthorizationUrl;
|
||||
use std::time::Duration;
|
||||
//use wasm_bindgen::JsCast;
|
||||
|
||||
|
||||
async fn refresh_flow(mut mize: Mize, old_secrets: ClientSecrets) -> MizeResult<ClientSecrets> {
|
||||
out!("Refresh flow...");
|
||||
let issuer = mize.get_config("auth.client.issuer")?.value_string()?;
|
||||
let client_id = mize.get_config("auth.client.client_id")?.value_string()?;
|
||||
|
||||
let issuer = IssuerUrl::new(issuer)?;
|
||||
let client_id = ClientId::new(client_id);
|
||||
|
||||
let http_client = HttpClient::new();
|
||||
|
||||
// Teach openidconnect-rs about the device authorization url.
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
struct MyExtraProviderMetadata {
|
||||
device_authorization_endpoint: String,
|
||||
}
|
||||
use openidconnect::core::{
|
||||
CoreAuthDisplay, CoreClaimName, CoreClaimType, CoreClient, CoreClientAuthMethod,
|
||||
CoreGrantType, CoreJsonWebKey, CoreJweContentEncryptionAlgorithm,
|
||||
CoreJweKeyManagementAlgorithm, CoreResponseMode, CoreResponseType,
|
||||
CoreSubjectIdentifierType,
|
||||
};
|
||||
use openidconnect::AdditionalProviderMetadata;
|
||||
use openidconnect::ProviderMetadata;
|
||||
impl AdditionalProviderMetadata for MyExtraProviderMetadata {}
|
||||
type MyProviderMetadata = ProviderMetadata<
|
||||
MyExtraProviderMetadata,
|
||||
CoreAuthDisplay,
|
||||
CoreClientAuthMethod,
|
||||
CoreClaimName,
|
||||
CoreClaimType,
|
||||
CoreGrantType,
|
||||
CoreJweContentEncryptionAlgorithm,
|
||||
CoreJweKeyManagementAlgorithm,
|
||||
CoreJsonWebKey,
|
||||
CoreResponseMode,
|
||||
CoreResponseType,
|
||||
CoreSubjectIdentifierType,
|
||||
>;
|
||||
|
||||
// Discover provider metadata
|
||||
let provider_metadata: MyProviderMetadata =
|
||||
MyProviderMetadata::discover_async(issuer, &http_client).await?;
|
||||
|
||||
let device_auth_url = provider_metadata
|
||||
.additional_metadata()
|
||||
.device_authorization_endpoint
|
||||
.clone();
|
||||
|
||||
let client = CoreClient::from_provider_metadata(
|
||||
provider_metadata,
|
||||
client_id,
|
||||
None, // public client
|
||||
)
|
||||
.set_device_authorization_url(DeviceAuthorizationUrl::new(device_auth_url)?);
|
||||
|
||||
let refresh_token = RefreshToken::new(old_secrets.refresh_token);
|
||||
let res = client
|
||||
.exchange_refresh_token(&refresh_token)?
|
||||
.request_async(&http_client)
|
||||
.await?;
|
||||
let expires_in = res.expires_in().ok_or(mize_err!(
|
||||
"no expiry information was in token response of the refresh flow"
|
||||
))?;
|
||||
|
||||
return Ok(ClientSecrets {
|
||||
id_token: res.id_token().unwrap().to_string(),
|
||||
access_token: res.access_token().secret().to_owned(),
|
||||
refresh_token: res.refresh_token().unwrap().secret().to_owned(),
|
||||
expiry: Utc::now() + expires_in,
|
||||
});
|
||||
}
|
||||
|
||||
async fn auth_flow(mut mize: Mize) -> MizeResult<ClientSecrets> {
|
||||
let issuer = mize.get_config("auth.client.issuer")?.value_string()?;
|
||||
let client_id = mize.get_config("auth.client.client_id")?.value_string()?;
|
||||
|
||||
let issuer = IssuerUrl::new(issuer)?;
|
||||
let client_id = ClientId::new(client_id);
|
||||
|
||||
let http_client = HttpClient::new();
|
||||
|
||||
// Teach openidconnect-rs about the device authorization url.
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
struct MyExtraProviderMetadata {
|
||||
device_authorization_endpoint: String,
|
||||
}
|
||||
use openidconnect::core::{
|
||||
CoreAuthDisplay, CoreClaimName, CoreClaimType, CoreClient, CoreClientAuthMethod,
|
||||
CoreGrantType, CoreJsonWebKey, CoreJweContentEncryptionAlgorithm,
|
||||
CoreJweKeyManagementAlgorithm, CoreResponseMode, CoreResponseType,
|
||||
CoreSubjectIdentifierType,
|
||||
};
|
||||
use openidconnect::AdditionalProviderMetadata;
|
||||
use openidconnect::ProviderMetadata;
|
||||
impl AdditionalProviderMetadata for MyExtraProviderMetadata {}
|
||||
type MyProviderMetadata = ProviderMetadata<
|
||||
MyExtraProviderMetadata,
|
||||
CoreAuthDisplay,
|
||||
CoreClientAuthMethod,
|
||||
CoreClaimName,
|
||||
CoreClaimType,
|
||||
CoreGrantType,
|
||||
CoreJweContentEncryptionAlgorithm,
|
||||
CoreJweKeyManagementAlgorithm,
|
||||
CoreJsonWebKey,
|
||||
CoreResponseMode,
|
||||
CoreResponseType,
|
||||
CoreSubjectIdentifierType,
|
||||
>;
|
||||
|
||||
// Discover provider metadata
|
||||
let provider_metadata: MyProviderMetadata =
|
||||
MyProviderMetadata::discover_async(issuer, &http_client).await?;
|
||||
|
||||
let device_auth_url = provider_metadata
|
||||
.additional_metadata()
|
||||
.device_authorization_endpoint
|
||||
.clone();
|
||||
|
||||
let client = CoreClient::from_provider_metadata(
|
||||
provider_metadata,
|
||||
client_id,
|
||||
None, // public client
|
||||
)
|
||||
.set_device_authorization_url(DeviceAuthorizationUrl::new(device_auth_url)?);
|
||||
|
||||
// ===== DEVICE AUTH REQUEST =====
|
||||
let details: Result<DeviceAuthorizationResponse<EmptyExtraDeviceAuthorizationFields>, _> =
|
||||
client
|
||||
.exchange_device_code()
|
||||
.add_scope(Scope::new("openid".into()))
|
||||
.add_scope(Scope::new("profile".into()))
|
||||
.add_scope(Scope::new("offline_access".into()))
|
||||
.request_async(&http_client)
|
||||
.await;
|
||||
|
||||
let details = match details {
|
||||
Ok(val) => val,
|
||||
Err(err) => {
|
||||
out!("err: {:?}", err);
|
||||
out!("err: {}", err);
|
||||
panic!();
|
||||
}
|
||||
};
|
||||
|
||||
#[cfg(feature = "target-obsidian")] {
|
||||
console_log!("details: {:?}", details);
|
||||
use std::str::FromStr;
|
||||
|
||||
use js_sys::{Array, Function, JsString, Reflect};
|
||||
use mize::error::IntoMizeResultJsValue;
|
||||
use wasm_bindgen::convert::TryFromJsValue;
|
||||
|
||||
let obsidian = mize.get_part_native::<crate::ObsidianPart>("obsidian")?;
|
||||
let show_device_code = Reflect::get(&obsidian.plugin(), &JsValue::from_str("showDeviceCode")).mres()?;
|
||||
let args = Array::new();
|
||||
args.push(&JsValue::from_str(details.user_code().secret().as_str()));
|
||||
args.push(&JsValue::from_str(details.verification_uri_complete().unwrap().secret().as_str()));
|
||||
|
||||
Reflect::apply(&show_device_code.dyn_into::<Function>().mres()?, obsidian.plugin(), &args).mres()?;
|
||||
}
|
||||
|
||||
#[cfg(feature = "target-os")] {
|
||||
out!(
|
||||
"If your browser does not open automatically, open this URL in it:\n{}\n",
|
||||
details.verification_uri_complete().unwrap().secret()
|
||||
);
|
||||
out!(
|
||||
"Or enter this PPC login code:\n{}\n",
|
||||
details.user_code().secret()
|
||||
);
|
||||
|
||||
if let Some(_) = details.verification_uri_complete() {
|
||||
let _ = webbrowser::open(details.verification_uri_complete().unwrap().secret());
|
||||
}
|
||||
}
|
||||
|
||||
let token_response = client
|
||||
.exchange_device_access_token(&details)?
|
||||
.request_async(
|
||||
&http_client,
|
||||
async |dur: Duration| {
|
||||
#[cfg(feature = "target-os")] {
|
||||
tokio::time::sleep(dur).await;
|
||||
}
|
||||
#[cfg(feature = "target-js")] {
|
||||
console_log!("sleeping");
|
||||
use wasm_bindgen::prelude::*;
|
||||
use wasm_bindgen_futures::JsFuture;
|
||||
let promise = js_sys::Promise::new(&mut |resolve, _| {
|
||||
web_sys::window()
|
||||
.unwrap()
|
||||
.set_timeout_with_callback_and_timeout_and_arguments_0(&resolve, dur.as_millis() as i32)
|
||||
.unwrap();
|
||||
});
|
||||
|
||||
// Await the JS Promise like a Rust Future
|
||||
let _ = JsFuture::from(promise).await;
|
||||
|
||||
}
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
|
||||
// ===== TOKENS =====
|
||||
let id_token = token_response
|
||||
.extra_fields()
|
||||
.id_token()
|
||||
.ok_or(mize_err!("no id_token"))?;
|
||||
|
||||
let refresh = token_response
|
||||
.refresh_token()
|
||||
.ok_or(mize_err!("no refresh token present"))?;
|
||||
|
||||
let expires_in = token_response.expires_in().ok_or(mize_err!(
|
||||
"no expiry information was in token response of the refresh flow"
|
||||
))?;
|
||||
|
||||
Ok(ClientSecrets {
|
||||
id_token: id_token.to_string(),
|
||||
access_token: token_response.access_token().secret().to_string(),
|
||||
refresh_token: refresh.secret().to_owned(),
|
||||
expiry: Utc::now() + expires_in,
|
||||
})
|
||||
}
|
||||
|
||||
fn update_spacetimedb_token(new_token: &str) -> MizeResult<()> {
|
||||
// Resolve config directory
|
||||
let config_dir = match std::env::var("XDG_CONFIG_HOME") {
|
||||
Ok(val) => PathBuf::from(val),
|
||||
Err(_) => {
|
||||
let home = std::env::var("HOME")?;
|
||||
std::path::PathBuf::from(home).join(".config")
|
||||
}
|
||||
};
|
||||
|
||||
let file_path = config_dir.join("spacetime").join("cli.toml");
|
||||
|
||||
if !file_path.exists() {
|
||||
warn!("spacetime config file does not exist, not updating token there");
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
// Read existing file (or create empty document if it doesn't exist)
|
||||
let content = std::fs::read_to_string(&file_path).unwrap_or_default();
|
||||
let mut data = ItemData::from_toml(content.as_str())?;
|
||||
|
||||
// Update the field
|
||||
data.set_path("spacetimedb_token", new_token)?;
|
||||
|
||||
// Write back
|
||||
std::fs::write(&file_path, data.to_toml()?)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
10
parts/cli/src/lib.rs
Normal file
10
parts/cli/src/lib.rs
Normal file
@@ -0,0 +1,10 @@
|
||||
use mize_macros::{include_native_simple, part_impl};
|
||||
use clap::Command;
|
||||
|
||||
#[part_impl]
|
||||
pub fn part() -> mize::Instance {
|
||||
// Register the command with the new macro syntax
|
||||
include_native_simple!("Command", clap::Command, [new]);
|
||||
|
||||
mize::Instance::new()
|
||||
}
|
||||
70
parts/mize.dev/src/generated.rs
Normal file
70
parts/mize.dev/src/generated.rs
Normal file
@@ -0,0 +1,70 @@
|
||||
use mize::prelude::*;
|
||||
|
||||
|
||||
pub struct CliPart {
|
||||
id: MizeId
|
||||
}
|
||||
|
||||
impl MizePart for CliPart {
|
||||
fn id(&self) -> MizeId {
|
||||
self.id.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl CliPart {
|
||||
pub fn get(name: &str) -> MizeResult<CliPart> {
|
||||
let mut mize = get_global_mize()?;
|
||||
let part = mize.get_part(name)?;
|
||||
Ok(CliPart { id: part.id() })
|
||||
}
|
||||
pub fn run(&self, cmdline: Vec<String>) -> MizeResult<()> {
|
||||
let cmdline = cmdline.into_item_data();
|
||||
let mut args = ItemData::new();
|
||||
args.set_path("0", cmdline)?;
|
||||
dyn_call(self.id.clone(), "run", args)?;
|
||||
Ok(())
|
||||
}
|
||||
pub fn set_command(&self, name: &str) -> MizeResult<()> {
|
||||
let name = name.into_item_data();
|
||||
let mut args = ItemData::new();
|
||||
args.set_path("0", name)?;
|
||||
dyn_call(self.id.clone(), "set_command", args)?;
|
||||
Ok(())
|
||||
}
|
||||
pub fn subcommand<T: Fn(ArgMatches) -> MizeResult<()> + Send + Sync + 'static>(&self, subcmd: Command, action: T) -> MizeResult<()> {
|
||||
let mut subcmd_arg = ItemData::new();
|
||||
subcmd_arg.set_path("_native", subcmd.native_id)?;
|
||||
let mut action_arg = ItemData::from_native(action)?;
|
||||
let mut args = ItemData::new();
|
||||
args.set_path("0", subcmd_arg)?;
|
||||
args.set_path("1", action_arg)?;
|
||||
dyn_call(self.id.clone(), "subcommand", args)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// the clap::Command native stuff
|
||||
pub struct Command {
|
||||
native_id: u32
|
||||
}
|
||||
|
||||
pub struct ArgMatches {
|
||||
native_id: u32
|
||||
}
|
||||
|
||||
pub struct Action {
|
||||
native_id: u32
|
||||
}
|
||||
|
||||
impl Command {
|
||||
pub fn new(name: &str) -> MizeResult<Command> {
|
||||
let name = name.into_item_data();
|
||||
let mut args = ItemData::new();
|
||||
args.set_path("0", name)?;
|
||||
let mut mize = get_global_mize()?;
|
||||
let id = mize.id_new_dots("cli.Command")?;
|
||||
let data = dyn_call(id.clone(), "new", args)?;
|
||||
let native_id: u32 = data.get_path("_native")?.native_take()?;
|
||||
Ok(Command { native_id })
|
||||
}
|
||||
}
|
||||
63
parts/mize.dev/src/lib.rs
Normal file
63
parts/mize.dev/src/lib.rs
Normal file
@@ -0,0 +1,63 @@
|
||||
use std::fs::create_dir_all;
|
||||
use mize::prelude::*;
|
||||
use cmd_lib::run_cmd as sh;
|
||||
use mize::tracing::info;
|
||||
|
||||
// a static version of what would be generated.
|
||||
// as we need mize.dev to generate anything.
|
||||
mod generated;
|
||||
use generated::*;
|
||||
|
||||
// spacetime gen command: sh!(RUST_LOG=error RUST_BACKTRACE=0 spacetime generate --build-options="--lint-dir=" -p $SRC/mize/marts/spacetime -o $SRC/mize/marts/generated/src/spacetime --lang rust)?;
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
extern "C" fn part_dev() -> MizeResult<()> {
|
||||
let mut mize = get_global_mize()?;
|
||||
let mut cli = generated::CliPart::get("cli")?;
|
||||
cli.set_command("dev");
|
||||
|
||||
cli.subcommand(Command::new("test")?, |_| {println!("hello from test command"); Ok(())})?;
|
||||
//cli.subcommand(Command::new("publish").alias("p"), publish_mize)?;
|
||||
//cli.subcommand(Command::new("genBindings").alias("b"), gen_bindings)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn publish_mize(_ : &ArgMatches, mut mize: Mize) -> MizeResult<()> {
|
||||
let mize_src = mize.get_config("mize.src")?.value_string()?;
|
||||
let cargo_dir = std::env::home_dir().unwrap().join(".cargo");
|
||||
|
||||
let targets = vec![
|
||||
"x86_64-pc-windows-gnu",
|
||||
"aarch64-pc-windows-gnullvm",
|
||||
|
||||
"x86_64-unknown-linux-gnu",
|
||||
"aarch64-unknown-linux-gnu",
|
||||
"armv7-unknown-linux-gnueabihf",
|
||||
"arm-unknown-linux-gnueabi",
|
||||
|
||||
"aarch64-apple-darwin",
|
||||
"x86_64-apple-darwin",
|
||||
];
|
||||
|
||||
for target_name in targets {
|
||||
let docker_image = match target_name {
|
||||
"aarch64-apple-darwin" => "darwin",
|
||||
"x86_64-apple-darwin" => "darwin",
|
||||
_ => "base",
|
||||
};
|
||||
sh!(docker build -t mize-build-$docker_image $mize_src/parts/mize.dev/targets/$docker_image)?;
|
||||
let res = sh!(docker run -it --rm -v $cargo_dir/registry:/usr/local/cargo/registry -v $cargo_dir/git:/usr/local/cargo/git -v $mize_src:/src -v $mize_src/target:/src/target mize-build-$docker_image cargo build --target $target_name --release);
|
||||
if res.is_err() {
|
||||
info!("build for {target_name} failed...");
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn gen_bindings(_ : &ArgMatches, mize: Mize) -> MizeResult<()> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user