Skip to content
Closed
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
2 changes: 1 addition & 1 deletion crates/ironclaw_auth/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ pub use oauth::{
OAuthCallbackStateKind, OAuthClientId, OAuthExtraParam, OAuthProviderIdentity,
OAuthProviderIdentitySubject, OAuthRedirectUri, OAuthScopeParam, OAuthState,
OAuthTokenResponse, PkceCodeChallenge, SLACK_PERSONAL_AUTHORIZATION_ENDPOINT,
SLACK_PERSONAL_PROVIDER_ID, SLACK_PERSONAL_TOKEN_ENDPOINT, authorization_code_hash,
SLACK_PERSONAL_TOKEN_ENDPOINT, SLACK_PROVIDER_ID, authorization_code_hash,
build_authorization_url, build_authorization_url_with_scope_param,
build_google_authorization_url, is_allowed_google_scope, opaque_state_hash,
parse_google_callback_scopes, parse_google_requested_scopes, pkce_s256_challenge,
Expand Down
4 changes: 2 additions & 2 deletions crates/ironclaw_auth/src/oauth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ pub const GOOGLE_GMAIL_MODIFY_SCOPE: &str = "https://www.googleapis.com/auth/gma
///
/// Deliberately distinct from the bot Slack extension (`slack`) so a user
/// token can never collide with the workspace bot token.
pub const SLACK_PERSONAL_PROVIDER_ID: &str = "slack_personal";
pub const SLACK_PROVIDER_ID: &str = "slack";
/// Slack OAuth v2 authorization endpoint (user-token consent).
pub const SLACK_PERSONAL_AUTHORIZATION_ENDPOINT: &str = "https://slack.com/oauth/v2/authorize";
/// Slack OAuth v2 token endpoint (`oauth.v2.access`).
Expand Down Expand Up @@ -990,7 +990,7 @@ mod tests {
OAuthAuthorizationEndpoint::new(SLACK_PERSONAL_AUTHORIZATION_ENDPOINT).unwrap();
let slack_client = OAuthClientId::new("slack-client-id").unwrap();
let slack_redirect = OAuthRedirectUri::new(
"http://127.0.0.1:3000/api/reborn/product-auth/oauth/slack_personal/callback",
"http://127.0.0.1:3000/api/reborn/product-auth/oauth/slack/callback",
)
.unwrap();
let slack_state = OAuthState::new("teststatevalue").unwrap();
Expand Down
78 changes: 68 additions & 10 deletions crates/ironclaw_first_party_extensions/assets/slack/manifest.toml

Large diffs are not rendered by default.

This file was deleted.

22 changes: 11 additions & 11 deletions crates/ironclaw_host_runtime/tests/github_wasm_runtime_contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -991,7 +991,7 @@ async fn host_runtime_services_missing_github_runtime_secret_blocks_on_auth() {
/// Audit F-010: per-user token isolation for the `slack_user` first-party
/// tool. A `slack_user` capability dispatch must inject the *authenticated
/// user's personal* Slack token — the `xoxp-` user token resolved from the
/// per-user `slack_personal` product-auth account — as the
/// per-user `slack` product-auth account — as the
/// `Authorization: Bearer` header on the slack.com egress, and never the
/// workspace bot (`xoxb-`) token. This mirrors the github/google search
/// injection contracts above, driven through the full `invoke_capability`
Expand All @@ -1010,7 +1010,7 @@ async fn host_runtime_services_injects_personal_xoxp_token_for_slack_user_search
);
let secret_store = Arc::new(InMemorySecretStore::new());
let slot_handle = SecretHandle::new("slack_user_token").unwrap();
let account_access_secret = SecretHandle::new("slack_personal_access").unwrap();
let account_access_secret = SecretHandle::new("slack_access").unwrap();
let services = HostRuntimeServices::new(
Arc::new(registry_with_slack_user_package()),
Arc::new(filesystem_with_slack_user_package()),
Expand All @@ -1021,7 +1021,7 @@ async fn host_runtime_services_injects_personal_xoxp_token_for_slack_user_search
},
Obligation::InjectCredentialAccountOnce {
handle: slot_handle,
provider: RuntimeCredentialAccountProviderId::new("slack_personal").unwrap(),
provider: RuntimeCredentialAccountProviderId::new("slack").unwrap(),
setup: ironclaw_host_api::RuntimeCredentialAccountSetup::OAuth {
scopes: slack_user_scopes(),
},
Expand Down Expand Up @@ -1081,7 +1081,7 @@ async fn host_runtime_services_injects_personal_xoxp_token_for_slack_user_search
);
assert_eq!(requests[0].policy, policy);
// The injected credential is the per-user personal xoxp token that was
// resolved from the `slack_personal` account and stored under this scope.
// resolved from the `slack` account and stored under this scope.
let authorization = requests[0]
.headers
.iter()
Expand All @@ -1102,13 +1102,13 @@ async fn host_runtime_services_injects_personal_xoxp_token_for_slack_user_search
);
}

/// Audit F-010 companion: a MISSING `slack_personal` account must gate the
/// Audit F-010 companion: a MISSING `slack` account must gate the
/// `slack_user` tool on auth — it must never silently fall back to another
/// credential (e.g. the workspace bot token). Mirrors the github
/// missing-secret contract: the resolver returns `AuthRequired`, and no
/// slack.com egress happens.
#[tokio::test]
async fn host_runtime_services_missing_slack_personal_account_blocks_slack_user_on_auth() {
async fn host_runtime_services_missing_slack_account_blocks_slack_user_on_auth() {
let capability_id = CapabilityId::new("slack.search_messages").unwrap();
let scope = sample_scope(InvocationId::new());
let policy = slack_policy();
Expand All @@ -1127,7 +1127,7 @@ async fn host_runtime_services_missing_slack_personal_account_blocks_slack_user_
},
Obligation::InjectCredentialAccountOnce {
handle: slot_handle,
provider: RuntimeCredentialAccountProviderId::new("slack_personal").unwrap(),
provider: RuntimeCredentialAccountProviderId::new("slack").unwrap(),
setup: ironclaw_host_api::RuntimeCredentialAccountSetup::OAuth {
scopes: slack_user_scopes(),
},
Expand Down Expand Up @@ -1169,7 +1169,7 @@ async fn host_runtime_services_missing_slack_personal_account_blocks_slack_user_
}
assert!(
network.requests().is_empty(),
"missing slack_personal account must block before slack.com egress"
"missing slack account must block before slack.com egress"
);
}

Expand Down Expand Up @@ -2003,7 +2003,7 @@ impl RuntimeCredentialAccountResolver for FixedGoogleRuntimeCredentialAccountRes
// `invoke_capability`, and assert the per-user personal-token injection.

/// Credential-account resolver for the `slack_user` tool. Asserts the runtime
/// asks for the per-user *personal* account (`slack_personal`) on behalf of the
/// asks for the per-user *personal* account (`slack`) on behalf of the
/// `slack_user` extension — never a workspace/bot credential — before handing
/// back the fixed access-secret handle (or an auth-required error).
#[derive(Debug)]
Expand All @@ -2018,7 +2018,7 @@ impl RuntimeCredentialAccountResolver for FixedSlackRuntimeCredentialAccountReso
&self,
request: RuntimeCredentialAccountRequest<'_>,
) -> Result<RuntimeCredentialAccessSecret, CredentialStageError> {
assert_eq!(request.provider.as_str(), "slack_personal");
assert_eq!(request.provider.as_str(), "slack");
assert_eq!(request.requester_extension.as_str(), "slack");
assert_eq!(request.provider_scopes, self.expected_scopes.as_slice());
self.result
Expand Down Expand Up @@ -2641,7 +2641,7 @@ macro_rules! slack_enrichment_services_for_test {
},
Obligation::InjectCredentialAccountOnce {
handle: SecretHandle::new("slack_user_token").unwrap(),
provider: RuntimeCredentialAccountProviderId::new("slack_personal").unwrap(),
provider: RuntimeCredentialAccountProviderId::new("slack").unwrap(),
setup: ironclaw_host_api::RuntimeCredentialAccountSetup::OAuth {
scopes: $scopes,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5162,7 +5162,7 @@ async fn list_extensions_projects_channel_surface_with_directions_and_connection
// whose summary declares an inbound+outbound channel surface projects a
// typed `channel` surface with its connect affordance — there is no
// separate connectable-channel registry or route.
let mut summary = extension_summary("slack_bot", Vec::new(), None);
let mut summary = extension_summary("slack", Vec::new(), None);
summary.surface_kinds = vec![CapabilitySurfaceKind::Channel];
summary.channel_directions = Some(LifecycleChannelDirections {
inbound: true,
Expand Down Expand Up @@ -5196,7 +5196,7 @@ async fn list_extensions_projects_channel_surface_with_directions_and_connection
let info = response
.extensions
.iter()
.find(|extension| extension.package_ref.id.as_str() == "slack_bot")
.find(|extension| extension.package_ref.id.as_str() == "slack")
.expect("channel extension listed");
let channel = info
.surfaces
Expand Down
9 changes: 4 additions & 5 deletions crates/ironclaw_reborn_composition/src/blocked_auth_resume.rs
Original file line number Diff line number Diff line change
Expand Up @@ -342,8 +342,7 @@ mod tests {

fn slack_requirement() -> RuntimeCredentialAuthRequirement {
RuntimeCredentialAuthRequirement {
provider: RuntimeCredentialAccountProviderId::new("slack_personal")
.expect("provider id"),
provider: RuntimeCredentialAccountProviderId::new("slack").expect("provider id"),
setup: RuntimeCredentialAccountSetup::OAuth { scopes: Vec::new() },
requester_extension: ExtensionId::new("slack").expect("extension id"),
provider_scopes: Vec::new(),
Expand Down Expand Up @@ -550,7 +549,7 @@ mod tests {

fanout
.dispatch_auth_continuation(event(
"slack_personal",
"slack",
AuthContinuationRef::TurnGateResume {
turn_run_ref: TurnRunRef::new(primary.run_id.to_string())
.expect("turn run ref"),
Expand Down Expand Up @@ -586,7 +585,7 @@ mod tests {
let (fanout, coordinator, _inner) = fanout_with(snapshot, false);

fanout
.dispatch_auth_continuation(event("slack_personal", AuthContinuationRef::SetupOnly))
.dispatch_auth_continuation(event("slack", AuthContinuationRef::SetupOnly))
.await
.expect("dispatch succeeds");

Expand All @@ -612,7 +611,7 @@ mod tests {
let (fanout, coordinator, inner) = fanout_with(snapshot, true);

let error = fanout
.dispatch_auth_continuation(event("slack_personal", AuthContinuationRef::SetupOnly))
.dispatch_auth_continuation(event("slack", AuthContinuationRef::SetupOnly))
.await
.expect_err("resume failures must prevent dispatched acknowledgement");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -414,7 +414,7 @@ output_schema_ref = "schemas/search.output.json"
#[tokio::test]
async fn filesystem_catalog_skips_reserved_host_bundled_extension_ids() {
let fs = InMemoryBackend::default();
for id in ["gmail", "slack_bot"] {
for id in ["gmail", "slack"] {
fs.write_file(
&VirtualPath::new(format!("/system/extensions/{id}/manifest.toml")).unwrap(),
b"not parsed because the id is host-bundled",
Expand All @@ -429,7 +429,7 @@ output_schema_ref = "schemas/search.output.json"
.await
.unwrap();
assert_eq!(catalog.search("").count(), 0);
assert_eq!(catalog.search("slack_bot").count(), 0);
assert_eq!(catalog.search("slack").count(), 0);
}

#[tokio::test]
Expand Down
Loading
Loading