ppc login works in mme main

This commit is contained in:
Sebastian Moser
2026-11-01 14:49:38 +01:00
parent 66b8713e3a
commit 6eedb1826a
68 changed files with 4123 additions and 1467 deletions

View File

@@ -0,0 +1,39 @@
cargo-features = ["profile-rustflags"]
[package]
name = "mize-part-auth-client"
version = "0.1.0"
edition = "2024"
[lib]
name = "mize_part_auth_client"
crate-type = [ "cdylib", "rlib" ]
[features]
default = [ "target-os" ]
target-os = [ "webbrowser" ]
target-js = []
target-obsidian = [ "target-js" ]
[dependencies]
mize = { workspace = true }
log = "0.4.34"
mize-generated-auth = { path = "./generated" }
webbrowser = { version = "1.2.0", optional = true }
serde = "1.0.228"
serde_json = "1.0.149"
tokio = { version = "1.49.0", default-features = false, features = ["time"] }
openidconnect = { version = "4", features = ["reqwest", "rustls-tls"], default-features = false }
reqwest = { version = "0.11", default-features = false, features = ["rustls-tls-webpki-roots"] }
chrono = { version = "0.4.45", features = [], default-features = false }
[profile.dev]
rustflags = ["-C", "prefer-dynamic", "-C", "link-args=-Wl,--export-dynamic"]
[profile.release]
rustflags = ["-C", "prefer-dynamic"]

View File

@@ -0,0 +1,11 @@
[package]
name = "mize-generated-auth"
version = "0.1.0"
edition = "2024"
[lib]
name = "mize_generated_auth"
path = "src/lib.rs"
[dependencies]
mize = { workspace = true }

View File

@@ -0,0 +1,92 @@
// AUTO-GENERATED by mize dev gen -- do not edit manually
use mize::prelude::*;
use mize::config::MizeFun;
pub struct CliPart {
id: MizeId
}
impl MizePart for CliPart {
fn id(&self) -> MizeResult<MizeId> {
Ok(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 set_command(&self, arg0: &str) -> MizeResult<()> {
let mut args = ItemData::new();
args.set_path("0", arg0.into_item_data())?;
dyn_call(self.id.clone(), "set_command", args)?;
Ok(())
}
pub fn subcommand(&self, arg0: Command, arg1: MizeFun) -> MizeResult<()> {
let mut args = ItemData::new();
let mut __arg0 = ItemData::new();
__arg0.set_path("_native", arg0.native_num)?;
args.set_path("0", __arg0)?;
args.set_path("1", ItemData::fun(arg1)?)?;
dyn_call(self.id.clone(), "subcommand", args)?;
Ok(())
}
pub fn run(&self, arg0: Vec<String>) -> MizeResult<()> {
let mut args = ItemData::new();
args.set_path("0", arg0.into_item_data())?;
dyn_call(self.id.clone(), "run", args)?;
Ok(())
}
pub fn opt_state(&self) -> MizeResult<ItemData> {
get_config(&format!("{}/state", self.id.dot()))
}
}
// native type: Command (clap::Command)
pub struct Command {
pub native_num: u32
}
impl FromItemData for Command {
fn from_itemdata(data: ItemData) -> MizeResult<Self> {
Ok(Command { native_num: data.get_path("_native")?.value_u32()? })
}
}
impl Command {
pub fn new(arg0: &str) -> MizeResult<Command> {
let mut args = ItemData::new();
args.set_path("0", arg0.into_item_data())?;
let mut mize = get_global_mize()?;
let id = mize.id_new_dots("cli.Command")?;
let data = dyn_call(id, "new", args)?;
let native_num: u32 = data.get_path("_native")?.value_u32()?;
Ok(Command { native_num })
}
}
// native type: ArgMatches (clap::ArgMatches)
pub struct ArgMatches {
pub native_num: u32
}
impl FromItemData for ArgMatches {
fn from_itemdata(data: ItemData) -> MizeResult<Self> {
Ok(ArgMatches { native_num: data.get_path("_native")?.value_u32()? })
}
}
impl ArgMatches {
}
// part id string: cli

2
parts/auth.client/part Normal file
View File

@@ -0,0 +1,2 @@
id = "auth.client"
gen = [ "cli" ]

View File

@@ -0,0 +1,718 @@
use chrono::{DateTime, TimeDelta, Utc};
use mize::prelude::*;
use openidconnect::{
ClientId, DeviceAuthorizationResponse, EmptyExtraDeviceAuthorizationFields, IssuerUrl,
OAuth2TokenResponse, RefreshToken, Scope, TokenResponse,
};
use serde::{Deserialize, Serialize};
use tokio::runtime::Runtime;
use std::fs::OpenOptions;
use std::io::Write;
use std::{path::PathBuf, thread};
use tracing::{info, warn};
use mize_generated_auth::*;
// 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())})
}
#[derive(Default, Serialize, Deserialize, Clone, Debug)]
pub struct ClientSecrets {
id_token: String,
access_token: String,
refresh_token: String,
expiry: DateTime<Utc>,
}
#[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(())
}
}
*/
async fn test() {
let client = reqwest::Client::builder().tls_built_in_root_certs(true).no_proxy().build().unwrap();
let req = client.get("https://orf.at").build().unwrap();
let res = client.execute(req).await.unwrap();
println!("res: {:?}", res);
}
#[unsafe(no_mangle)]
pub extern "C" fn part_auth_client() -> MizeResult<()> {
opt("secrets")
.fun(Box::new(|| {
let mut mize = get_global_mize()?;
if let Ok(secrets) = load_secrets(&mut mize) {
if secrets.expiry < Utc::now() {
if let Ok(fresh_secrets) = mize.spawn_async_blocking("refresh", refresh_flow(mize.clone(), secrets.clone())) {
return ItemData::from_serializable(Some(fresh_secrets));
}
}
return ItemData::from_serializable(Some(secrets));
} else {
return ItemData::from_serializable::<Option<ClientSecrets>>(None);
}
}));
method("login", "() -> ()", |_args| {
#[cfg(feature = "target-os")] {
println!("hiiiii");
let mut mize = get_global_mize()?;
// i hate rust.
let runtime = Runtime::new()?;
let guard = runtime.enter();
std::mem::forget(guard);
println!("hiiiii");
match do_login() {
Ok(secrets) => {
store_secrets(&mut mize, &secrets)?;
notify_mme_login_state(true)?;
}
Err(err) => {
notify_mme_login_state(false)?;
return Err(err);
}
}
}
Ok(ItemData::new())
});
let mut 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);
}
// cli command for os
#[cfg(feature = "target-os")] {
let cli = CliPart::get("cli")?;
//let mut cli = mize.get_part_native::<CliPart>("cli")?;
cli.set_command("ppc");
cli.subcommand(Command::new("login")?, Box::new(|_| {
let mut mize = get_global_mize()?;
dyn_call(mize.id_new_dots("auth.client")?, "login", ItemData::new())?;
Ok(ItemData::new())
}))?;
}
// 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
}),
})?;
}
Ok(())
}
pub fn start_refresh_thread(_args: ItemData) -> MizeResult<ItemData> {
// background thread for os targets
#[cfg(feature = "target-os")] {
let mut mize = get_global_mize()?;
mize.spawn_background("auth.refresh", move || loop {
let mut mize = get_global_mize()?;
info!("hi from auth.refresh thread...");
thread::sleep(Duration::from_secs(33)); // TERRIBLE hack
let secrets = get_config("self.secrets")?.native::<ClientSecrets>()?;
let expiry = secrets.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();
}
}
*/
info!("auth.refresh: thread going to sleep for: {}", to_sleep);
thread::sleep(to_sleep.to_std()?);
info!("refresh_thread: sleeping done");
dyn_call(mize.id_new_dots("auth.client")?, "refresh", ItemData::new())?;
})?;
}
/*
// 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(ItemData::new())
}
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().expiry < Utc::now() {
// refresh if secrets are expired
secrets = match mize.spawn_async_blocking("auth.refresh", refresh_flow(mize.clone(), val)) {
Ok(secrets) => secrets,
Err(err) => {
out!("err refreshing secrets: {:?}", err);
mize.spawn_async_blocking("auth.login", auth_flow(mize.clone()))?
}
};
} else {
secrets = val;
}
} else {
secrets = mize.spawn_async_blocking("auth.login", auth_flow(mize.clone()))?;
}
Ok(secrets)
}
fn notify_mme_url_and_code(url: &str, code: &str) -> MizeResult<()> {
let mut mize = get_global_mize()?;
if mize.has_part("mme")? {
let mut args = ItemData::new();
args.set_path("0", url.to_owned().into_item_data())?;
args.set_path("1", code.to_owned().into_item_data())?;
dyn_call(mize.id_new_dots("mme")?, "set_url_and_code", args)?;
}
Ok(())
}
fn notify_mme_login_state(logged_in: bool) -> MizeResult<()> {
let mut mize = get_global_mize()?;
if mize.has_part("mme")? {
let mut args = ItemData::new();
args.set_path("0", logged_in.into_item_data())?;
dyn_call(mize.id_new_dots("mme")?, "set_login_state", args)?;
}
Ok(())
}
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 = openidconnect::reqwest::Client::builder().tls_built_in_root_certs(true).build()?;
// 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.map_err(|e| mize_err!("From reqwest::error::Error: {e}"))?;
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")] {
let url = details
.verification_uri_complete()
.map(|u| u.secret().clone())
.unwrap_or_default();
let code = details.user_code().secret().clone();
let _ = notify_mme_url_and_code(&url, &code);
out!(
"If your browser does not open automatically, open this URL in it:\n{}\n",
url
);
out!("Or enter this PPC login code:\n{}\n", code);
if !url.is_empty() {
let _ = webbrowser::open(&url);
}
}
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(())
}