use super::OptionValueExt;
#[macro_export]
macro_rules! auth_catalog_machine_dsl {
($rust_crate:literal, $rust_module:literal) => {
meerkat_machine_dsl::machine! {
machine AuthMachine {
version: 1,
rust: $rust_crate / $rust_module,
state {
lifecycle_phase: AuthLifecyclePhase,
expires_at: Option<u64>,
last_refresh: Option<u64>,
refresh_attempt: u64,
credential_present: bool,
oauth_browser_flow_ids: Set<String>,
oauth_browser_flow_providers: Map<String, String>,
oauth_browser_flow_redirect_uris: Map<String, String>,
oauth_browser_flow_expires_at_millis: Map<String, u64>,
oauth_device_flow_ids: Set<String>,
oauth_device_flow_providers: Map<String, String>,
oauth_device_flow_expires_at_millis: Map<String, u64>,
oauth_device_poll_ids: Set<String>,
oauth_outstanding_flow_count: u64,
}
init(Valid) {
expires_at = None,
last_refresh = None,
refresh_attempt = 0,
credential_present = false,
oauth_browser_flow_ids = EmptySet,
oauth_browser_flow_providers = EmptyMap,
oauth_browser_flow_redirect_uris = EmptyMap,
oauth_browser_flow_expires_at_millis = EmptyMap,
oauth_device_flow_ids = EmptySet,
oauth_device_flow_providers = EmptyMap,
oauth_device_flow_expires_at_millis = EmptyMap,
oauth_device_poll_ids = EmptySet,
oauth_outstanding_flow_count = 0,
}
terminal [Released]
phase AuthLifecyclePhase {
Valid,
Expiring,
Refreshing,
ReauthRequired,
Released,
}
input AuthMachineInput {
Acquire { expires_at_ts: Option<u64> },
MarkExpiring,
BeginRefresh,
CompleteRefresh { new_expires_at: Option<u64>, now_ts: u64 },
RefreshFailedTransient,
RefreshFailedPermanent,
MarkReauthRequired,
ClearCredentialLifecycle,
Release,
AdmitOAuthBrowserFlow { flow_id: String, provider: String, redirect_uri: String, expires_at_millis: u64, max_outstanding_flows: u64 },
VerifyOAuthBrowserFlow { flow_id: String, provider: String, redirect_uri: String, now_millis: u64 },
ConsumeOAuthBrowserFlow { flow_id: String, provider: String, redirect_uri: String, now_millis: u64 },
ExpireOAuthBrowserFlow { flow_id: String },
AdmitOAuthDeviceFlow { flow_id: String, provider: String, expires_at_millis: u64, max_outstanding_flows: u64 },
VerifyOAuthDeviceFlow { flow_id: String, provider: String, now_millis: u64 },
BeginOAuthDevicePoll { flow_id: String, provider: String, now_millis: u64 },
FinishOAuthDevicePoll { flow_id: String },
ConsumeOAuthDeviceFlow { flow_id: String, provider: String, now_millis: u64 },
ExpireOAuthDeviceFlow { flow_id: String },
}
effect AuthMachineEffect {
EmitLifecycleEvent { new_state: AuthLifecyclePhase },
WakeRefreshLoop,
}
disposition EmitLifecycleEvent => external handoff auth_lease_lifecycle_publication,
disposition WakeRefreshLoop => local,
transition Acquire {
on input Acquire { expires_at_ts }
update {
self.expires_at = expires_at_ts;
self.refresh_attempt = 0;
self.credential_present = true;
}
to Valid
emit EmitLifecycleEvent { new_state: self.lifecycle_phase }
}
transition MarkExpiring {
on input MarkExpiring
guard { self.lifecycle_phase == Phase::Valid }
to Expiring
emit EmitLifecycleEvent { new_state: self.lifecycle_phase }
}
transition BeginRefreshFromValid {
on input BeginRefresh
guard { self.lifecycle_phase == Phase::Valid }
to Refreshing
emit EmitLifecycleEvent { new_state: self.lifecycle_phase }
emit WakeRefreshLoop
}
transition BeginRefreshFromExpiring {
on input BeginRefresh
guard { self.lifecycle_phase == Phase::Expiring }
to Refreshing
emit EmitLifecycleEvent { new_state: self.lifecycle_phase }
emit WakeRefreshLoop
}
transition CompleteRefresh {
on input CompleteRefresh { new_expires_at, now_ts }
guard { self.lifecycle_phase == Phase::Refreshing }
update {
self.expires_at = new_expires_at;
self.last_refresh = Some(now_ts);
self.refresh_attempt = 0;
}
to Valid
emit EmitLifecycleEvent { new_state: self.lifecycle_phase }
}
transition RefreshFailedTransient {
on input RefreshFailedTransient
guard { self.lifecycle_phase == Phase::Refreshing }
update {
self.refresh_attempt = self.refresh_attempt + 1;
}
to Expiring
emit EmitLifecycleEvent { new_state: self.lifecycle_phase }
}
transition RefreshFailedPermanent {
on input RefreshFailedPermanent
guard { self.lifecycle_phase == Phase::Refreshing }
update {
self.refresh_attempt = self.refresh_attempt + 1;
}
to ReauthRequired
emit EmitLifecycleEvent { new_state: self.lifecycle_phase }
}
transition MarkReauthRequiredFromValid {
on input MarkReauthRequired
guard { self.lifecycle_phase == Phase::Valid }
to ReauthRequired
emit EmitLifecycleEvent { new_state: self.lifecycle_phase }
}
transition MarkReauthRequiredFromExpiring {
on input MarkReauthRequired
guard { self.lifecycle_phase == Phase::Expiring }
to ReauthRequired
emit EmitLifecycleEvent { new_state: self.lifecycle_phase }
}
transition MarkReauthRequiredFromRefreshing {
on input MarkReauthRequired
guard { self.lifecycle_phase == Phase::Refreshing }
to ReauthRequired
emit EmitLifecycleEvent { new_state: self.lifecycle_phase }
}
transition ClearCredentialLifecycle {
on input ClearCredentialLifecycle
update {
self.expires_at = None;
self.last_refresh = None;
self.refresh_attempt = 0;
self.credential_present = false;
}
to ReauthRequired
emit EmitLifecycleEvent { new_state: self.lifecycle_phase }
}
transition Release {
on input Release
update {
self.credential_present = false;
self.oauth_browser_flow_ids = EmptySet;
self.oauth_browser_flow_providers = EmptyMap;
self.oauth_browser_flow_redirect_uris = EmptyMap;
self.oauth_browser_flow_expires_at_millis = EmptyMap;
self.oauth_device_flow_ids = EmptySet;
self.oauth_device_flow_providers = EmptyMap;
self.oauth_device_flow_expires_at_millis = EmptyMap;
self.oauth_device_poll_ids = EmptySet;
self.oauth_outstanding_flow_count = 0;
}
to Released
emit EmitLifecycleEvent { new_state: self.lifecycle_phase }
}
transition AdmitOAuthBrowserFlow {
per_phase [Valid, Expiring, Refreshing, ReauthRequired]
on input AdmitOAuthBrowserFlow { flow_id, provider, redirect_uri, expires_at_millis, max_outstanding_flows }
guard "browser_flow_absent" { self.oauth_browser_flow_ids.contains(flow_id) == false }
guard "oauth_capacity_available" { self.oauth_outstanding_flow_count < max_outstanding_flows }
update {
self.oauth_browser_flow_ids.insert(flow_id);
self.oauth_browser_flow_providers.insert(flow_id, provider);
self.oauth_browser_flow_redirect_uris.insert(flow_id, redirect_uri);
self.oauth_browser_flow_expires_at_millis.insert(flow_id, expires_at_millis);
self.oauth_outstanding_flow_count = self.oauth_outstanding_flow_count + 1;
}
to Valid
emit EmitLifecycleEvent { new_state: self.lifecycle_phase }
}
transition VerifyOAuthBrowserFlow {
per_phase [Valid, Expiring, Refreshing, ReauthRequired]
on input VerifyOAuthBrowserFlow { flow_id, provider, redirect_uri, now_millis }
guard "browser_flow_present" { self.oauth_browser_flow_ids.contains(flow_id) }
guard "browser_flow_provider_matches" { self.oauth_browser_flow_providers.get_cloned(flow_id) == Some(provider) }
guard "browser_flow_redirect_uri_matches" { self.oauth_browser_flow_redirect_uris.get_cloned(flow_id) == Some(redirect_uri) }
guard "browser_flow_not_expired" { now_millis <= self.oauth_browser_flow_expires_at_millis.get_cloned(flow_id).get("value") }
update {}
to Valid
emit EmitLifecycleEvent { new_state: self.lifecycle_phase }
}
transition ConsumeOAuthBrowserFlow {
per_phase [Valid, Expiring, Refreshing, ReauthRequired]
on input ConsumeOAuthBrowserFlow { flow_id, provider, redirect_uri, now_millis }
guard "browser_flow_present" { self.oauth_browser_flow_ids.contains(flow_id) }
guard "browser_flow_provider_matches" { self.oauth_browser_flow_providers.get_cloned(flow_id) == Some(provider) }
guard "browser_flow_redirect_uri_matches" { self.oauth_browser_flow_redirect_uris.get_cloned(flow_id) == Some(redirect_uri) }
guard "browser_flow_not_expired" { now_millis <= self.oauth_browser_flow_expires_at_millis.get_cloned(flow_id).get("value") }
update {
self.oauth_browser_flow_ids.remove(flow_id);
self.oauth_browser_flow_providers.remove(flow_id);
self.oauth_browser_flow_redirect_uris.remove(flow_id);
self.oauth_browser_flow_expires_at_millis.remove(flow_id);
self.oauth_outstanding_flow_count = self.oauth_outstanding_flow_count - 1;
}
to Valid
emit EmitLifecycleEvent { new_state: self.lifecycle_phase }
}
transition ExpireOAuthBrowserFlow {
per_phase [Valid, Expiring, Refreshing, ReauthRequired]
on input ExpireOAuthBrowserFlow { flow_id }
guard "browser_flow_present" { self.oauth_browser_flow_ids.contains(flow_id) }
update {
self.oauth_browser_flow_ids.remove(flow_id);
self.oauth_browser_flow_providers.remove(flow_id);
self.oauth_browser_flow_redirect_uris.remove(flow_id);
self.oauth_browser_flow_expires_at_millis.remove(flow_id);
self.oauth_outstanding_flow_count = self.oauth_outstanding_flow_count - 1;
}
to Valid
emit EmitLifecycleEvent { new_state: self.lifecycle_phase }
}
transition AdmitOAuthDeviceFlow {
per_phase [Valid, Expiring, Refreshing, ReauthRequired]
on input AdmitOAuthDeviceFlow { flow_id, provider, expires_at_millis, max_outstanding_flows }
guard "device_flow_absent" { self.oauth_device_flow_ids.contains(flow_id) == false }
guard "oauth_capacity_available" { self.oauth_outstanding_flow_count < max_outstanding_flows }
update {
self.oauth_device_flow_ids.insert(flow_id);
self.oauth_device_flow_providers.insert(flow_id, provider);
self.oauth_device_flow_expires_at_millis.insert(flow_id, expires_at_millis);
self.oauth_device_poll_ids.remove(flow_id);
self.oauth_outstanding_flow_count = self.oauth_outstanding_flow_count + 1;
}
to Valid
emit EmitLifecycleEvent { new_state: self.lifecycle_phase }
}
transition VerifyOAuthDeviceFlow {
per_phase [Valid, Expiring, Refreshing, ReauthRequired]
on input VerifyOAuthDeviceFlow { flow_id, provider, now_millis }
guard "device_flow_present" { self.oauth_device_flow_ids.contains(flow_id) }
guard "device_flow_provider_matches" { self.oauth_device_flow_providers.get_cloned(flow_id) == Some(provider) }
guard "device_flow_not_expired" { now_millis <= self.oauth_device_flow_expires_at_millis.get_cloned(flow_id).get("value") }
update {}
to Valid
emit EmitLifecycleEvent { new_state: self.lifecycle_phase }
}
transition BeginOAuthDevicePoll {
per_phase [Valid, Expiring, Refreshing, ReauthRequired]
on input BeginOAuthDevicePoll { flow_id, provider, now_millis }
guard "device_flow_present" { self.oauth_device_flow_ids.contains(flow_id) }
guard "device_flow_provider_matches" { self.oauth_device_flow_providers.get_cloned(flow_id) == Some(provider) }
guard "device_flow_not_expired" { now_millis <= self.oauth_device_flow_expires_at_millis.get_cloned(flow_id).get("value") }
guard "device_poll_absent" { self.oauth_device_poll_ids.contains(flow_id) == false }
update {
self.oauth_device_poll_ids.insert(flow_id);
}
to Valid
emit EmitLifecycleEvent { new_state: self.lifecycle_phase }
}
transition FinishOAuthDevicePoll {
per_phase [Valid, Expiring, Refreshing, ReauthRequired]
on input FinishOAuthDevicePoll { flow_id }
guard "device_poll_present" { self.oauth_device_poll_ids.contains(flow_id) }
update {
self.oauth_device_poll_ids.remove(flow_id);
}
to Valid
emit EmitLifecycleEvent { new_state: self.lifecycle_phase }
}
transition ConsumeOAuthDeviceFlow {
per_phase [Valid, Expiring, Refreshing, ReauthRequired]
on input ConsumeOAuthDeviceFlow { flow_id, provider, now_millis }
guard "device_flow_present" { self.oauth_device_flow_ids.contains(flow_id) }
guard "device_flow_provider_matches" { self.oauth_device_flow_providers.get_cloned(flow_id) == Some(provider) }
guard "device_flow_not_expired" { now_millis <= self.oauth_device_flow_expires_at_millis.get_cloned(flow_id).get("value") }
update {
self.oauth_device_flow_ids.remove(flow_id);
self.oauth_device_flow_providers.remove(flow_id);
self.oauth_device_flow_expires_at_millis.remove(flow_id);
self.oauth_device_poll_ids.remove(flow_id);
self.oauth_outstanding_flow_count = self.oauth_outstanding_flow_count - 1;
}
to Valid
emit EmitLifecycleEvent { new_state: self.lifecycle_phase }
}
transition ExpireOAuthDeviceFlow {
per_phase [Valid, Expiring, Refreshing, ReauthRequired]
on input ExpireOAuthDeviceFlow { flow_id }
guard "device_flow_present" { self.oauth_device_flow_ids.contains(flow_id) }
update {
self.oauth_device_flow_ids.remove(flow_id);
self.oauth_device_flow_providers.remove(flow_id);
self.oauth_device_flow_expires_at_millis.remove(flow_id);
self.oauth_device_poll_ids.remove(flow_id);
self.oauth_outstanding_flow_count = self.oauth_outstanding_flow_count - 1;
}
to Valid
emit EmitLifecycleEvent { new_state: self.lifecycle_phase }
}
}
}
};
}
crate::auth_catalog_machine_dsl!("self", "catalog::dsl::auth_machine");