From 30fb05261628ddbf607fa93c1361183b2ffc229c Mon Sep 17 00:00:00 2001 From: Dalton Cherry Date: Thu, 21 May 2026 18:45:28 -0500 Subject: [PATCH 1/2] Add Clerk WASM plugin Clerk authentication and identity management integration (34 tools) for users, sessions, organizations, memberships, invitations, and allow/block list identifiers via the Clerk Backend API. Co-Authored-By: Crush --- Cargo.lock | 9 + README.md | 1 + dist/clerk.wasm | Bin 0 -> 210742 bytes manifest.json | 19 + plugins/clerk/Cargo.toml | 12 + plugins/clerk/README.md | 70 +++ plugins/clerk/src/handlers.rs | 830 ++++++++++++++++++++++++++++++++++ plugins/clerk/src/lib.rs | 481 ++++++++++++++++++++ plugins/clerk/src/tools.rs | 376 +++++++++++++++ 9 files changed, 1798 insertions(+) create mode 100755 dist/clerk.wasm create mode 100644 plugins/clerk/Cargo.toml create mode 100644 plugins/clerk/README.md create mode 100644 plugins/clerk/src/handlers.rs create mode 100644 plugins/clerk/src/lib.rs create mode 100644 plugins/clerk/src/tools.rs diff --git a/Cargo.lock b/Cargo.lock index 2650c9b..fbe7da4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,15 @@ dependencies = [ "switchboard-guest-sdk", ] +[[package]] +name = "clerk-wasm" +version = "0.1.0" +dependencies = [ + "serde", + "serde_json", + "switchboard-guest-sdk", +] + [[package]] name = "itoa" version = "1.0.18" diff --git a/README.md b/README.md index c9a7d63..d302533 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ A home for integrations that make more sense as standalone WASM modules than as | Plugin | Tools | Description | |--------|-------|-------------| | [bland](plugins/bland/) | 30 | Bland.ai voice AI: calls, transcripts, voices, pathways, inbound numbers, knowledge bases, org management, billing, audit logs | +| [clerk](plugins/clerk/) | 34 | Clerk authentication and identity: users, sessions, organizations, memberships, invitations, allow/block list identifiers | | [looker](plugins/looker/) | 23 | Looker BI: dashboards, Looks, LookML models, inline analytics queries, SQL Runner | Prebuilt binaries live in [`dist/`](dist/) and are referenced by [`manifest.json`](manifest.json). diff --git a/dist/clerk.wasm b/dist/clerk.wasm new file mode 100755 index 0000000000000000000000000000000000000000..700f255c2e548f6ad132841bd78ac2509a906436 GIT binary patch literal 210742 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 lP*@XE9Gob;1l$1`. | + +## Tools + +### Users + +| Tool | Description | +|------|-------------| +| `clerk_list_users` | Search and list Clerk users by email, phone, username, or user ID; filter by organization, banned/locked state, last activity | +| `clerk_get_user` | Get a user's full profile, email addresses, phone numbers, external accounts, metadata, and timestamps | +| `clerk_create_user` | Create a new Clerk user with email, phone, username, password, name, and metadata | +| `clerk_update_user` | Update a user's profile, metadata, password, or primary identifier | +| `clerk_delete_user` | Permanently delete a Clerk user | +| `clerk_ban_user` | Ban a user, preventing all sign-ins | +| `clerk_unban_user` | Lift a ban on a user | +| `clerk_lock_user` | Lock a user out of new sign-ins | +| `clerk_unlock_user` | Unlock a previously locked user | +| `clerk_list_user_organization_memberships` | List all organizations a user belongs to with their role | + +### Sessions + +| Tool | Description | +|------|-------------| +| `clerk_list_sessions` | List Clerk sessions filtered by user, client, or status | +| `clerk_get_session` | Get session details including user, status, expiry, and last activity | +| `clerk_revoke_session` | Revoke a session, signing the user out of that client | + +### Organizations + +| Tool | Description | +|------|-------------| +| `clerk_list_organizations` | List or search Clerk organizations (tenants, workspaces, teams) | +| `clerk_get_organization` | Get organization details by ID or slug | +| `clerk_create_organization` | Create a new organization with a name, slug, and creator user | +| `clerk_update_organization` | Update an organization's name, slug, max memberships, or metadata | +| `clerk_delete_organization` | Permanently delete an organization | +| `clerk_list_organization_memberships` | List members of an organization with their role | +| `clerk_create_organization_membership` | Add a user to an organization with a role | +| `clerk_update_organization_membership` | Change a member's role within an organization | +| `clerk_delete_organization_membership` | Remove a user from an organization | +| `clerk_list_organization_invitations` | List pending/accepted/revoked invitations for an organization | +| `clerk_create_organization_invitation` | Invite a user (by email) to an organization with a role | +| `clerk_revoke_organization_invitation` | Revoke a pending organization invitation | + +### Invitations (instance-level) + +| Tool | Description | +|------|-------------| +| `clerk_list_invitations` | List instance-level invitations sent to email addresses | +| `clerk_create_invitation` | Send an instance-level invitation to an email address | +| `clerk_revoke_invitation` | Revoke a pending instance-level invitation | + +### Allow / Block list + +| Tool | Description | +|------|-------------| +| `clerk_list_allowlist_identifiers` | List identifiers (emails, phones, domains) allowed to sign up | +| `clerk_create_allowlist_identifier` | Add an identifier to the sign-up allow list | +| `clerk_delete_allowlist_identifier` | Remove an identifier from the sign-up allow list | +| `clerk_list_blocklist_identifiers` | List identifiers blocked from signing up | +| `clerk_create_blocklist_identifier` | Add an identifier to the sign-up block list | +| `clerk_delete_blocklist_identifier` | Remove an identifier from the sign-up block list | diff --git a/plugins/clerk/src/handlers.rs b/plugins/clerk/src/handlers.rs new file mode 100644 index 0000000..fca76ec --- /dev/null +++ b/plugins/clerk/src/handlers.rs @@ -0,0 +1,830 @@ +use std::collections::HashMap; +use switchboard_guest_sdk as sdk; + +use crate::{clerk_delete, clerk_get, clerk_patch, clerk_post, path_escape, query_escape}; + +macro_rules! call { + ($call:expr) => { + match $call { + Ok(v) => v, + Err(e) => return sdk::err_result(&e), + } + }; +} + +fn json_result(v: &serde_json::Value) -> sdk::ToolResult { + match serde_json::to_string(v) { + Ok(s) => sdk::raw_result(s), + Err(e) => sdk::err_result(&format!("encode response: {e}")), + } +} + +fn required( + args: &HashMap, + key: &str, +) -> Result { + let value = sdk::arg_str(args, key); + if value.is_empty() { + Err(sdk::err_result(&format!("{key} is required"))) + } else { + Ok(value) + } +} + +fn add_str_param( + args: &HashMap, + params: &mut Vec<(String, String)>, + arg: &str, + key: &str, +) { + let value = sdk::arg_str(args, arg); + if !value.is_empty() { + params.push((key.to_string(), value)); + } +} + +fn add_int_param( + args: &HashMap, + params: &mut Vec<(String, String)>, + arg: &str, + key: &str, +) { + if let Some(value) = sdk::arg_int(args, arg) { + params.push((key.to_string(), value.to_string())); + } +} + +fn add_bool_param( + args: &HashMap, + params: &mut Vec<(String, String)>, + arg: &str, + key: &str, +) { + if let Some(value) = sdk::arg_bool(args, arg) { + params.push((key.to_string(), value.to_string())); + } +} + +/// Repeats an array-valued query parameter as `key=v1&key=v2` after splitting on commas. +fn add_repeated_param( + args: &HashMap, + params: &mut Vec<(String, String)>, + arg: &str, + key: &str, +) { + let value = sdk::arg_str(args, arg); + if value.is_empty() { + return; + } + for piece in value.split(',') { + let trimmed = piece.trim(); + if !trimmed.is_empty() { + params.push((key.to_string(), trimmed.to_string())); + } + } +} + +fn append_query(path: &str, params: &[(String, String)]) -> String { + if params.is_empty() { + return path.to_string(); + } + let query = params + .iter() + .map(|(key, value)| format!("{}={}", query_escape(key), query_escape(value))) + .collect::>() + .join("&"); + format!("{path}?{query}") +} + +fn insert_string_body( + args: &HashMap, + body: &mut serde_json::Map, + key: &str, +) { + let value = sdk::arg_str(args, key); + if !value.is_empty() { + body.insert(key.to_string(), serde_json::json!(value)); + } +} + +fn insert_i64_body( + args: &HashMap, + body: &mut serde_json::Map, + key: &str, +) { + if let Some(value) = sdk::arg_int(args, key) { + body.insert(key.to_string(), serde_json::json!(value)); + } +} + +fn insert_bool_body( + args: &HashMap, + body: &mut serde_json::Map, + key: &str, +) { + if let Some(value) = sdk::arg_bool(args, key) { + body.insert(key.to_string(), serde_json::json!(value)); + } +} + +fn insert_json_body( + args: &HashMap, + body: &mut serde_json::Map, + key: &str, +) -> Result<(), String> { + match args.get(key) { + Some(serde_json::Value::String(s)) if !s.is_empty() => { + let parsed: serde_json::Value = + serde_json::from_str(s).map_err(|e| format!("{key} must be valid JSON: {e}"))?; + body.insert(key.to_string(), parsed); + } + Some(v) if !v.is_null() && !matches!(v, serde_json::Value::String(s) if s.is_empty()) => { + body.insert(key.to_string(), v.clone()); + } + _ => {} + } + Ok(()) +} + +/// Coerces a value that may arrive as a JSON array or a comma-separated string into a JSON array. +fn insert_string_array_body( + args: &HashMap, + body: &mut serde_json::Map, + key: &str, +) -> Result<(), String> { + match args.get(key) { + Some(serde_json::Value::Array(arr)) => { + body.insert(key.to_string(), serde_json::Value::Array(arr.clone())); + } + Some(serde_json::Value::String(s)) if !s.is_empty() => { + let trimmed = s.trim(); + if trimmed.starts_with('[') { + let parsed: serde_json::Value = serde_json::from_str(trimmed) + .map_err(|e| format!("{key} must be a JSON array: {e}"))?; + if !parsed.is_array() { + return Err(format!("{key} must be a JSON array")); + } + body.insert(key.to_string(), parsed); + } else { + let values: Vec = trimmed + .split(',') + .filter_map(|p| { + let t = p.trim(); + if t.is_empty() { + None + } else { + Some(serde_json::Value::String(t.to_string())) + } + }) + .collect(); + body.insert(key.to_string(), serde_json::Value::Array(values)); + } + } + _ => {} + } + Ok(()) +} + +// ── Users ─────────────────────────────────────────────────────────────────── + +pub fn list_users(args: HashMap) -> sdk::ToolResult { + let mut params: Vec<(String, String)> = vec![ + ( + "limit".into(), + sdk::arg_int(&args, "limit") + .unwrap_or(10) + .clamp(1, 500) + .to_string(), + ), + ( + "offset".into(), + sdk::arg_int(&args, "offset") + .unwrap_or(0) + .max(0) + .to_string(), + ), + ]; + add_str_param(&args, &mut params, "order_by", "order_by"); + add_str_param(&args, &mut params, "query", "query"); + add_repeated_param(&args, &mut params, "email_address", "email_address"); + add_repeated_param(&args, &mut params, "phone_number", "phone_number"); + add_repeated_param(&args, &mut params, "username", "username"); + add_repeated_param(&args, &mut params, "user_id", "user_id"); + add_repeated_param(&args, &mut params, "external_id", "external_id"); + add_repeated_param(&args, &mut params, "organization_id", "organization_id"); + add_bool_param(&args, &mut params, "banned", "banned"); + add_bool_param(&args, &mut params, "locked", "locked"); + add_int_param( + &args, + &mut params, + "last_active_at_since", + "last_active_at_since", + ); + + let v = call!(clerk_get(&append_query("/users", ¶ms))); + json_result(&v) +} + +pub fn get_user(args: HashMap) -> sdk::ToolResult { + let id = match required(&args, "user_id") { + Ok(v) => v, + Err(e) => return e, + }; + let v = call!(clerk_get(&format!("/users/{}", path_escape(&id)))); + json_result(&v) +} + +pub fn create_user(args: HashMap) -> sdk::ToolResult { + let mut body = serde_json::Map::new(); + + for key in ["email_address", "phone_number", "web3_wallet"] { + if let Err(e) = insert_string_array_body(&args, &mut body, key) { + return sdk::err_result(&e); + } + } + for key in [ + "username", + "password", + "password_digest", + "password_hasher", + "first_name", + "last_name", + "external_id", + "created_at", + ] { + insert_string_body(&args, &mut body, key); + } + for key in ["skip_password_checks", "skip_password_requirement"] { + insert_bool_body(&args, &mut body, key); + } + for key in ["public_metadata", "private_metadata", "unsafe_metadata"] { + if let Err(e) = insert_json_body(&args, &mut body, key) { + return sdk::err_result(&e); + } + } + + let v = call!(clerk_post("/users", &serde_json::Value::Object(body))); + json_result(&v) +} + +pub fn update_user(args: HashMap) -> sdk::ToolResult { + let id = match required(&args, "user_id") { + Ok(v) => v, + Err(e) => return e, + }; + + let mut body = serde_json::Map::new(); + for key in [ + "first_name", + "last_name", + "username", + "primary_email_address_id", + "primary_phone_number_id", + "primary_web3_wallet_id", + "profile_image_id", + "password", + "password_digest", + "password_hasher", + "external_id", + ] { + insert_string_body(&args, &mut body, key); + } + insert_bool_body(&args, &mut body, "sign_out_of_other_sessions"); + for key in ["public_metadata", "private_metadata", "unsafe_metadata"] { + if let Err(e) = insert_json_body(&args, &mut body, key) { + return sdk::err_result(&e); + } + } + + let v = call!(clerk_patch( + &format!("/users/{}", path_escape(&id)), + &serde_json::Value::Object(body) + )); + json_result(&v) +} + +pub fn delete_user(args: HashMap) -> sdk::ToolResult { + let id = match required(&args, "user_id") { + Ok(v) => v, + Err(e) => return e, + }; + let v = call!(clerk_delete(&format!("/users/{}", path_escape(&id)))); + json_result(&v) +} + +fn user_action(args: HashMap, action: &str) -> sdk::ToolResult { + let id = match required(&args, "user_id") { + Ok(v) => v, + Err(e) => return e, + }; + let v = call!(clerk_post( + &format!("/users/{}/{}", path_escape(&id), action), + &serde_json::json!({}) + )); + json_result(&v) +} + +pub fn ban_user(args: HashMap) -> sdk::ToolResult { + user_action(args, "ban") +} + +pub fn unban_user(args: HashMap) -> sdk::ToolResult { + user_action(args, "unban") +} + +pub fn lock_user(args: HashMap) -> sdk::ToolResult { + user_action(args, "lock") +} + +pub fn unlock_user(args: HashMap) -> sdk::ToolResult { + user_action(args, "unlock") +} + +pub fn list_user_organization_memberships( + args: HashMap, +) -> sdk::ToolResult { + let id = match required(&args, "user_id") { + Ok(v) => v, + Err(e) => return e, + }; + + let params: Vec<(String, String)> = vec![ + ( + "limit".into(), + sdk::arg_int(&args, "limit") + .unwrap_or(10) + .clamp(1, 100) + .to_string(), + ), + ( + "offset".into(), + sdk::arg_int(&args, "offset") + .unwrap_or(0) + .max(0) + .to_string(), + ), + ]; + + let v = call!(clerk_get(&append_query( + &format!("/users/{}/organization_memberships", path_escape(&id)), + ¶ms + ))); + json_result(&v) +} + +// ── Sessions ──────────────────────────────────────────────────────────────── + +pub fn list_sessions(args: HashMap) -> sdk::ToolResult { + let mut params: Vec<(String, String)> = Vec::new(); + add_str_param(&args, &mut params, "client_id", "client_id"); + add_str_param(&args, &mut params, "user_id", "user_id"); + add_str_param(&args, &mut params, "status", "status"); + + let v = call!(clerk_get(&append_query("/sessions", ¶ms))); + json_result(&v) +} + +pub fn get_session(args: HashMap) -> sdk::ToolResult { + let id = match required(&args, "session_id") { + Ok(v) => v, + Err(e) => return e, + }; + let v = call!(clerk_get(&format!("/sessions/{}", path_escape(&id)))); + json_result(&v) +} + +pub fn revoke_session(args: HashMap) -> sdk::ToolResult { + let id = match required(&args, "session_id") { + Ok(v) => v, + Err(e) => return e, + }; + let v = call!(clerk_post( + &format!("/sessions/{}/revoke", path_escape(&id)), + &serde_json::json!({}) + )); + json_result(&v) +} + +// ── Organizations ─────────────────────────────────────────────────────────── + +pub fn list_organizations(args: HashMap) -> sdk::ToolResult { + let mut params: Vec<(String, String)> = vec![ + ( + "limit".into(), + sdk::arg_int(&args, "limit") + .unwrap_or(10) + .clamp(1, 500) + .to_string(), + ), + ( + "offset".into(), + sdk::arg_int(&args, "offset") + .unwrap_or(0) + .max(0) + .to_string(), + ), + ]; + add_bool_param( + &args, + &mut params, + "include_members_count", + "include_members_count", + ); + add_str_param(&args, &mut params, "order_by", "order_by"); + add_str_param(&args, &mut params, "query", "query"); + add_repeated_param(&args, &mut params, "user_id", "user_id"); + + let v = call!(clerk_get(&append_query("/organizations", ¶ms))); + json_result(&v) +} + +pub fn get_organization(args: HashMap) -> sdk::ToolResult { + let id = match required(&args, "organization_id") { + Ok(v) => v, + Err(e) => return e, + }; + let mut params: Vec<(String, String)> = Vec::new(); + add_bool_param( + &args, + &mut params, + "include_members_count", + "include_members_count", + ); + let v = call!(clerk_get(&append_query( + &format!("/organizations/{}", path_escape(&id)), + ¶ms + ))); + json_result(&v) +} + +pub fn create_organization(args: HashMap) -> sdk::ToolResult { + let name = match required(&args, "name") { + Ok(v) => v, + Err(e) => return e, + }; + let created_by = match required(&args, "created_by") { + Ok(v) => v, + Err(e) => return e, + }; + + let mut body = serde_json::Map::new(); + body.insert("name".into(), serde_json::json!(name)); + body.insert("created_by".into(), serde_json::json!(created_by)); + insert_string_body(&args, &mut body, "slug"); + insert_i64_body(&args, &mut body, "max_allowed_memberships"); + for key in ["public_metadata", "private_metadata"] { + if let Err(e) = insert_json_body(&args, &mut body, key) { + return sdk::err_result(&e); + } + } + + let v = call!(clerk_post( + "/organizations", + &serde_json::Value::Object(body) + )); + json_result(&v) +} + +pub fn update_organization(args: HashMap) -> sdk::ToolResult { + let id = match required(&args, "organization_id") { + Ok(v) => v, + Err(e) => return e, + }; + + let mut body = serde_json::Map::new(); + insert_string_body(&args, &mut body, "name"); + insert_string_body(&args, &mut body, "slug"); + insert_i64_body(&args, &mut body, "max_allowed_memberships"); + insert_bool_body(&args, &mut body, "admin_delete_enabled"); + for key in ["public_metadata", "private_metadata"] { + if let Err(e) = insert_json_body(&args, &mut body, key) { + return sdk::err_result(&e); + } + } + + let v = call!(clerk_patch( + &format!("/organizations/{}", path_escape(&id)), + &serde_json::Value::Object(body) + )); + json_result(&v) +} + +pub fn delete_organization(args: HashMap) -> sdk::ToolResult { + let id = match required(&args, "organization_id") { + Ok(v) => v, + Err(e) => return e, + }; + let v = call!(clerk_delete(&format!( + "/organizations/{}", + path_escape(&id) + ))); + json_result(&v) +} + +pub fn list_organization_memberships(args: HashMap) -> sdk::ToolResult { + let id = match required(&args, "organization_id") { + Ok(v) => v, + Err(e) => return e, + }; + let mut params: Vec<(String, String)> = vec![ + ( + "limit".into(), + sdk::arg_int(&args, "limit") + .unwrap_or(10) + .clamp(1, 500) + .to_string(), + ), + ( + "offset".into(), + sdk::arg_int(&args, "offset") + .unwrap_or(0) + .max(0) + .to_string(), + ), + ]; + add_str_param(&args, &mut params, "order_by", "order_by"); + + let v = call!(clerk_get(&append_query( + &format!("/organizations/{}/memberships", path_escape(&id)), + ¶ms + ))); + json_result(&v) +} + +pub fn create_organization_membership(args: HashMap) -> sdk::ToolResult { + let org_id = match required(&args, "organization_id") { + Ok(v) => v, + Err(e) => return e, + }; + let user_id = match required(&args, "user_id") { + Ok(v) => v, + Err(e) => return e, + }; + let role = match required(&args, "role") { + Ok(v) => v, + Err(e) => return e, + }; + + let body = serde_json::json!({ + "user_id": user_id, + "role": role, + }); + let v = call!(clerk_post( + &format!("/organizations/{}/memberships", path_escape(&org_id)), + &body + )); + json_result(&v) +} + +pub fn update_organization_membership(args: HashMap) -> sdk::ToolResult { + let org_id = match required(&args, "organization_id") { + Ok(v) => v, + Err(e) => return e, + }; + let user_id = match required(&args, "user_id") { + Ok(v) => v, + Err(e) => return e, + }; + let role = match required(&args, "role") { + Ok(v) => v, + Err(e) => return e, + }; + + let body = serde_json::json!({ "role": role }); + let v = call!(clerk_patch( + &format!( + "/organizations/{}/memberships/{}", + path_escape(&org_id), + path_escape(&user_id) + ), + &body + )); + json_result(&v) +} + +pub fn delete_organization_membership(args: HashMap) -> sdk::ToolResult { + let org_id = match required(&args, "organization_id") { + Ok(v) => v, + Err(e) => return e, + }; + let user_id = match required(&args, "user_id") { + Ok(v) => v, + Err(e) => return e, + }; + let v = call!(clerk_delete(&format!( + "/organizations/{}/memberships/{}", + path_escape(&org_id), + path_escape(&user_id) + ))); + json_result(&v) +} + +pub fn list_organization_invitations(args: HashMap) -> sdk::ToolResult { + let id = match required(&args, "organization_id") { + Ok(v) => v, + Err(e) => return e, + }; + + let mut params: Vec<(String, String)> = vec![ + ( + "limit".into(), + sdk::arg_int(&args, "limit") + .unwrap_or(10) + .clamp(1, 500) + .to_string(), + ), + ( + "offset".into(), + sdk::arg_int(&args, "offset") + .unwrap_or(0) + .max(0) + .to_string(), + ), + ]; + add_repeated_param(&args, &mut params, "status", "status"); + + let v = call!(clerk_get(&append_query( + &format!("/organizations/{}/invitations", path_escape(&id)), + ¶ms + ))); + json_result(&v) +} + +pub fn create_organization_invitation(args: HashMap) -> sdk::ToolResult { + let org_id = match required(&args, "organization_id") { + Ok(v) => v, + Err(e) => return e, + }; + let email_address = match required(&args, "email_address") { + Ok(v) => v, + Err(e) => return e, + }; + let role = match required(&args, "role") { + Ok(v) => v, + Err(e) => return e, + }; + + let mut body = serde_json::Map::new(); + body.insert("email_address".into(), serde_json::json!(email_address)); + body.insert("role".into(), serde_json::json!(role)); + insert_string_body(&args, &mut body, "inviter_user_id"); + insert_string_body(&args, &mut body, "redirect_url"); + for key in ["public_metadata", "private_metadata"] { + if let Err(e) = insert_json_body(&args, &mut body, key) { + return sdk::err_result(&e); + } + } + + let v = call!(clerk_post( + &format!("/organizations/{}/invitations", path_escape(&org_id)), + &serde_json::Value::Object(body) + )); + json_result(&v) +} + +pub fn revoke_organization_invitation(args: HashMap) -> sdk::ToolResult { + let org_id = match required(&args, "organization_id") { + Ok(v) => v, + Err(e) => return e, + }; + let invitation_id = match required(&args, "invitation_id") { + Ok(v) => v, + Err(e) => return e, + }; + + let mut body = serde_json::Map::new(); + insert_string_body(&args, &mut body, "requesting_user_id"); + + let v = call!(clerk_post( + &format!( + "/organizations/{}/invitations/{}/revoke", + path_escape(&org_id), + path_escape(&invitation_id) + ), + &serde_json::Value::Object(body) + )); + json_result(&v) +} + +// ── Invitations ───────────────────────────────────────────────────────────── + +pub fn list_invitations(args: HashMap) -> sdk::ToolResult { + let mut params: Vec<(String, String)> = vec![ + ( + "limit".into(), + sdk::arg_int(&args, "limit") + .unwrap_or(10) + .clamp(1, 500) + .to_string(), + ), + ( + "offset".into(), + sdk::arg_int(&args, "offset") + .unwrap_or(0) + .max(0) + .to_string(), + ), + ]; + add_str_param(&args, &mut params, "status", "status"); + add_str_param(&args, &mut params, "query", "query"); + add_str_param(&args, &mut params, "order_by", "order_by"); + + let v = call!(clerk_get(&append_query("/invitations", ¶ms))); + json_result(&v) +} + +pub fn create_invitation(args: HashMap) -> sdk::ToolResult { + let email_address = match required(&args, "email_address") { + Ok(v) => v, + Err(e) => return e, + }; + + let mut body = serde_json::Map::new(); + body.insert("email_address".into(), serde_json::json!(email_address)); + insert_string_body(&args, &mut body, "redirect_url"); + insert_string_body(&args, &mut body, "template_slug"); + insert_bool_body(&args, &mut body, "notify"); + insert_bool_body(&args, &mut body, "ignore_existing"); + insert_i64_body(&args, &mut body, "expires_in_days"); + if let Err(e) = insert_json_body(&args, &mut body, "public_metadata") { + return sdk::err_result(&e); + } + + let v = call!(clerk_post("/invitations", &serde_json::Value::Object(body))); + json_result(&v) +} + +pub fn revoke_invitation(args: HashMap) -> sdk::ToolResult { + let id = match required(&args, "invitation_id") { + Ok(v) => v, + Err(e) => return e, + }; + let v = call!(clerk_post( + &format!("/invitations/{}/revoke", path_escape(&id)), + &serde_json::json!({}) + )); + json_result(&v) +} + +// ── Allow / Block list ────────────────────────────────────────────────────── + +pub fn list_allowlist_identifiers(_args: HashMap) -> sdk::ToolResult { + let v = call!(clerk_get("/allowlist_identifiers")); + json_result(&v) +} + +pub fn create_allowlist_identifier(args: HashMap) -> sdk::ToolResult { + let identifier = match required(&args, "identifier") { + Ok(v) => v, + Err(e) => return e, + }; + let mut body = serde_json::Map::new(); + body.insert("identifier".into(), serde_json::json!(identifier)); + insert_bool_body(&args, &mut body, "notify"); + + let v = call!(clerk_post( + "/allowlist_identifiers", + &serde_json::Value::Object(body) + )); + json_result(&v) +} + +pub fn delete_allowlist_identifier(args: HashMap) -> sdk::ToolResult { + let id = match required(&args, "identifier_id") { + Ok(v) => v, + Err(e) => return e, + }; + let v = call!(clerk_delete(&format!( + "/allowlist_identifiers/{}", + path_escape(&id) + ))); + json_result(&v) +} + +pub fn list_blocklist_identifiers(_args: HashMap) -> sdk::ToolResult { + let v = call!(clerk_get("/blocklist_identifiers")); + json_result(&v) +} + +pub fn create_blocklist_identifier(args: HashMap) -> sdk::ToolResult { + let identifier = match required(&args, "identifier") { + Ok(v) => v, + Err(e) => return e, + }; + let body = serde_json::json!({ "identifier": identifier }); + + let v = call!(clerk_post("/blocklist_identifiers", &body)); + json_result(&v) +} + +pub fn delete_blocklist_identifier(args: HashMap) -> sdk::ToolResult { + let id = match required(&args, "identifier_id") { + Ok(v) => v, + Err(e) => return e, + }; + let v = call!(clerk_delete(&format!( + "/blocklist_identifiers/{}", + path_escape(&id) + ))); + json_result(&v) +} diff --git a/plugins/clerk/src/lib.rs b/plugins/clerk/src/lib.rs new file mode 100644 index 0000000..0276af2 --- /dev/null +++ b/plugins/clerk/src/lib.rs @@ -0,0 +1,481 @@ +mod handlers; +mod tools; + +use std::collections::HashMap; +use std::sync::Mutex; +use switchboard_guest_sdk as sdk; + +#[cfg(test)] +#[no_mangle] +pub extern "C" fn host_http_request(_ptr_size: u64) -> u64 { + 0 +} + +#[cfg(test)] +#[no_mangle] +pub extern "C" fn host_log(_ptr: u32, _size: u32) {} + +const API_BASE: &str = "https://api.clerk.com/v1"; + +struct Config { + secret_key: String, +} + +static CONFIG: Mutex> = Mutex::new(None); + +fn with_config(f: F) -> Result +where + F: FnOnce(&Config) -> R, +{ + let guard = CONFIG.lock().map_err(|e| e.to_string())?; + match guard.as_ref() { + Some(c) => Ok(f(c)), + None => Err("clerk: not configured".into()), + } +} + +#[no_mangle] +pub extern "C" fn name() -> u64 { + sdk::leaked_string("clerk") +} + +#[no_mangle] +pub extern "C" fn metadata() -> u64 { + sdk::leaked_metadata(&sdk::PluginMetadata { + name: "clerk".into(), + version: "0.1.0".into(), + abi_version: 1, + description: "Clerk authentication and identity management — users, sessions, organizations, memberships, invitations, and allow/block list identifiers via the Clerk Backend API.".into(), + author: "daltoniam".into(), + homepage: "https://github.com/daltoniam/switchboard_plugins".into(), + license: "MIT".into(), + capabilities: vec!["http".into()], + credential_keys: vec!["secret_key".into()], + plain_text_keys: vec![], + optional_keys: vec![], + placeholders: HashMap::from([( + "secret_key".into(), + "Clerk secret key (sk_test_... or sk_live_...)".into(), + )]), + }) +} + +#[no_mangle] +pub extern "C" fn tools() -> u64 { + let defs = tools::tool_definitions(); + let data = serde_json::to_vec(&defs).unwrap_or_default(); + sdk::leaked_result(&data) +} + +#[no_mangle] +pub extern "C" fn configure(ptr_size: u64) -> u64 { + let input = sdk::read_input(ptr_size); + let creds: HashMap = match serde_json::from_slice(&input) { + Ok(c) => c, + Err(e) => return sdk::leaked_string(&format!("clerk: invalid credentials JSON: {e}")), + }; + + let secret_key = creds.get("secret_key").cloned().unwrap_or_default(); + if secret_key.is_empty() { + return sdk::leaked_string("clerk: secret_key is required"); + } + + *CONFIG.lock().unwrap() = Some(Config { secret_key }); + 0 +} + +#[no_mangle] +pub extern "C" fn execute(ptr_size: u64) -> u64 { + let input = sdk::read_input(ptr_size); + let req: sdk::ExecuteRequest = match serde_json::from_slice(&input) { + Ok(r) => r, + Err(e) => { + let r = sdk::err_result(&format!("invalid request: {e}")); + let data = serde_json::to_vec(&r).unwrap_or_default(); + return sdk::leaked_result(&data); + } + }; + + let result = dispatch(&req.tool_name, req.args); + let data = serde_json::to_vec(&result).unwrap_or_default(); + sdk::leaked_result(&data) +} + +#[no_mangle] +pub extern "C" fn healthy() -> i32 { + // JWKS is the cheapest authenticated read; succeeds iff the secret key is valid. + match clerk_get("/jwks") { + Ok(_) => 1, + Err(_) => 0, + } +} + +#[no_mangle] +pub extern "C" fn compact_specs() -> u64 { + sdk::leaked_compact_specs(&compact_spec_map()) +} + +type HandlerFn = fn(HashMap) -> sdk::ToolResult; + +fn dispatch(tool_name: &str, args: HashMap) -> sdk::ToolResult { + let handler: Option = match tool_name { + // Users + "clerk_list_users" => Some(handlers::list_users), + "clerk_get_user" => Some(handlers::get_user), + "clerk_create_user" => Some(handlers::create_user), + "clerk_update_user" => Some(handlers::update_user), + "clerk_delete_user" => Some(handlers::delete_user), + "clerk_ban_user" => Some(handlers::ban_user), + "clerk_unban_user" => Some(handlers::unban_user), + "clerk_lock_user" => Some(handlers::lock_user), + "clerk_unlock_user" => Some(handlers::unlock_user), + "clerk_list_user_organization_memberships" => { + Some(handlers::list_user_organization_memberships) + } + // Sessions + "clerk_list_sessions" => Some(handlers::list_sessions), + "clerk_get_session" => Some(handlers::get_session), + "clerk_revoke_session" => Some(handlers::revoke_session), + // Organizations + "clerk_list_organizations" => Some(handlers::list_organizations), + "clerk_get_organization" => Some(handlers::get_organization), + "clerk_create_organization" => Some(handlers::create_organization), + "clerk_update_organization" => Some(handlers::update_organization), + "clerk_delete_organization" => Some(handlers::delete_organization), + "clerk_list_organization_memberships" => Some(handlers::list_organization_memberships), + "clerk_create_organization_membership" => Some(handlers::create_organization_membership), + "clerk_update_organization_membership" => Some(handlers::update_organization_membership), + "clerk_delete_organization_membership" => Some(handlers::delete_organization_membership), + "clerk_list_organization_invitations" => Some(handlers::list_organization_invitations), + "clerk_create_organization_invitation" => Some(handlers::create_organization_invitation), + "clerk_revoke_organization_invitation" => Some(handlers::revoke_organization_invitation), + // Invitations + "clerk_list_invitations" => Some(handlers::list_invitations), + "clerk_create_invitation" => Some(handlers::create_invitation), + "clerk_revoke_invitation" => Some(handlers::revoke_invitation), + // Allow/Block list + "clerk_list_allowlist_identifiers" => Some(handlers::list_allowlist_identifiers), + "clerk_create_allowlist_identifier" => Some(handlers::create_allowlist_identifier), + "clerk_delete_allowlist_identifier" => Some(handlers::delete_allowlist_identifier), + "clerk_list_blocklist_identifiers" => Some(handlers::list_blocklist_identifiers), + "clerk_create_blocklist_identifier" => Some(handlers::create_blocklist_identifier), + "clerk_delete_blocklist_identifier" => Some(handlers::delete_blocklist_identifier), + _ => None, + }; + + match handler { + Some(f) => f(args), + None => sdk::err_result(&format!("unknown tool: {tool_name}")), + } +} + +pub(crate) fn clerk_get(path: &str) -> Result { + do_request("GET", path, None) +} + +pub(crate) fn clerk_post( + path: &str, + body: &serde_json::Value, +) -> Result { + do_request("POST", path, Some(body)) +} + +pub(crate) fn clerk_patch( + path: &str, + body: &serde_json::Value, +) -> Result { + do_request("PATCH", path, Some(body)) +} + +pub(crate) fn clerk_delete(path: &str) -> Result { + do_request("DELETE", path, None) +} + +fn do_request( + method: &str, + path: &str, + body: Option<&serde_json::Value>, +) -> Result { + let secret_key = with_config(|c| c.secret_key.clone())?; + + let mut headers = HashMap::new(); + headers.insert("Authorization".into(), format!("Bearer {secret_key}")); + headers.insert("Accept".into(), "application/json".into()); + + let body_str = if let Some(b) = body { + headers.insert("Content-Type".into(), "application/json".into()); + serde_json::to_string(b).map_err(|e| format!("clerk: encode request: {e}"))? + } else { + String::new() + }; + + let req = sdk::HttpRequest { + method: method.into(), + url: format!("{API_BASE}{path}"), + headers, + body: body_str, + ..Default::default() + }; + + let resp = sdk::host_http_request(&req)?; + if resp.status >= 400 { + return Err(format!("clerk API error ({}): {}", resp.status, resp.body)); + } + if resp.status == 204 || resp.body.is_empty() { + return Ok(serde_json::json!({"status": "success"})); + } + serde_json::from_str(&resp.body).map_err(|e| format!("clerk: decode response: {e}")) +} + +pub(crate) fn path_escape(s: &str) -> String { + encode_component(s) +} + +pub(crate) fn query_escape(s: &str) -> String { + encode_component(s) +} + +fn encode_component(s: &str) -> String { + let mut out = String::with_capacity(s.len()); + for b in s.as_bytes() { + let c = *b; + let safe = c.is_ascii_alphanumeric() || matches!(c, b'-' | b'_' | b'.' | b'~'); + if safe { + out.push(c as char); + } else { + out.push_str(&format!("%{c:02X}")); + } + } + out +} + +fn compact_spec_map() -> HashMap> { + let mut s: HashMap> = HashMap::new(); + + // Users list — Clerk returns either a bare array or {data, total_count}. + // Spec both shapes so compaction always finds a match. + s.insert( + "clerk_list_users".into(), + vec![ + "total_count".into(), + "data[].id".into(), + "data[].username".into(), + "data[].first_name".into(), + "data[].last_name".into(), + "data[].primary_email_address_id".into(), + "data[].primary_phone_number_id".into(), + "data[].email_addresses[].id".into(), + "data[].email_addresses[].email_address".into(), + "data[].email_addresses[].verification.status".into(), + "data[].phone_numbers[].id".into(), + "data[].phone_numbers[].phone_number".into(), + "data[].banned".into(), + "data[].locked".into(), + "data[].created_at".into(), + "data[].updated_at".into(), + "data[].last_sign_in_at".into(), + "data[].last_active_at".into(), + // Fallback for bare-array responses + "[].id".into(), + "[].username".into(), + "[].first_name".into(), + "[].last_name".into(), + "[].primary_email_address_id".into(), + "[].email_addresses[].email_address".into(), + "[].phone_numbers[].phone_number".into(), + "[].banned".into(), + "[].locked".into(), + "[].created_at".into(), + "[].last_sign_in_at".into(), + "[].last_active_at".into(), + ], + ); + s.insert( + "clerk_list_user_organization_memberships".into(), + vec![ + "total_count".into(), + "data[].id".into(), + "data[].role".into(), + "data[].role_name".into(), + "data[].created_at".into(), + "data[].organization.id".into(), + "data[].organization.name".into(), + "data[].organization.slug".into(), + "data[].organization.members_count".into(), + ], + ); + s.insert( + "clerk_list_sessions".into(), + vec![ + "[].id".into(), + "[].user_id".into(), + "[].client_id".into(), + "[].status".into(), + "[].last_active_at".into(), + "[].expire_at".into(), + "[].abandon_at".into(), + "[].created_at".into(), + "[].latest_activity.country".into(), + "[].latest_activity.city".into(), + "[].latest_activity.is_mobile".into(), + "[].latest_activity.browser_name".into(), + "[].latest_activity.device_type".into(), + ], + ); + s.insert( + "clerk_list_organizations".into(), + vec![ + "total_count".into(), + "data[].id".into(), + "data[].name".into(), + "data[].slug".into(), + "data[].members_count".into(), + "data[].max_allowed_memberships".into(), + "data[].created_by".into(), + "data[].created_at".into(), + "data[].updated_at".into(), + ], + ); + s.insert( + "clerk_list_organization_memberships".into(), + vec![ + "total_count".into(), + "data[].id".into(), + "data[].role".into(), + "data[].role_name".into(), + "data[].created_at".into(), + "data[].public_user_data.user_id".into(), + "data[].public_user_data.identifier".into(), + "data[].public_user_data.first_name".into(), + "data[].public_user_data.last_name".into(), + "data[].organization.id".into(), + "data[].organization.slug".into(), + ], + ); + s.insert( + "clerk_list_organization_invitations".into(), + vec![ + "total_count".into(), + "data[].id".into(), + "data[].email_address".into(), + "data[].role".into(), + "data[].role_name".into(), + "data[].status".into(), + "data[].organization_id".into(), + "data[].created_at".into(), + "data[].updated_at".into(), + ], + ); + s.insert( + "clerk_list_invitations".into(), + vec![ + "[].id".into(), + "[].email_address".into(), + "[].status".into(), + "[].revoked".into(), + "[].created_at".into(), + "[].updated_at".into(), + "[].expires_at".into(), + "[].url".into(), + ], + ); + s.insert( + "clerk_list_allowlist_identifiers".into(), + vec![ + "[].id".into(), + "[].identifier".into(), + "[].identifier_type".into(), + "[].instance_id".into(), + "[].created_at".into(), + "[].updated_at".into(), + ], + ); + s.insert( + "clerk_list_blocklist_identifiers".into(), + vec![ + "[].id".into(), + "[].identifier".into(), + "[].identifier_type".into(), + "[].instance_id".into(), + "[].created_at".into(), + "[].updated_at".into(), + ], + ); + + s +} + +#[cfg(test)] +mod tests { + use super::*; + use std::collections::HashSet; + + #[test] + fn tool_definitions_have_required_metadata() { + let defs = tools::tool_definitions(); + assert_eq!(defs.len(), 34); + + let mut seen = HashSet::new(); + for def in defs { + assert!(def.name.starts_with("clerk_"), "{}", def.name); + assert!(!def.description.is_empty(), "{}", def.name); + assert!(seen.insert(def.name)); + } + } + + #[test] + fn entry_point_tools_have_start_here_guidance() { + for def in tools::tool_definitions() { + if matches!( + def.name.as_str(), + "clerk_list_users" + | "clerk_list_sessions" + | "clerk_list_organizations" + | "clerk_list_invitations" + | "clerk_list_allowlist_identifiers" + | "clerk_list_blocklist_identifiers" + ) { + assert!(def.description.contains("Start here"), "{}", def.name); + } + } + } + + #[test] + fn compact_specs_reference_known_tools() { + let defs = tools::tool_definitions() + .into_iter() + .map(|def| def.name) + .collect::>(); + for name in compact_spec_map().keys() { + assert!(defs.contains(name), "{name}"); + } + } + + #[test] + fn unknown_tool_returns_error_result() { + let result = dispatch("clerk_missing_tool", HashMap::new()); + assert!(result.is_error); + assert!(result.data.contains("unknown tool")); + } + + #[test] + fn dispatch_covers_every_tool() { + // No host_http_request available in unit tests, so handlers will return an + // error, but it must NOT be the "unknown tool" error from the dispatch + // fallthrough. Any other error proves the tool name routes to a handler. + for def in tools::tool_definitions() { + let result = dispatch(&def.name, HashMap::new()); + assert!( + !result.data.contains("unknown tool"), + "tool {} is not in dispatch", + def.name + ); + } + } + + #[test] + fn escapes_path_and_query_components() { + assert_eq!(path_escape("user_2abc/def+ghi"), "user_2abc%2Fdef%2Bghi"); + assert_eq!(query_escape("dale@example.com"), "dale%40example.com"); + } +} diff --git a/plugins/clerk/src/tools.rs b/plugins/clerk/src/tools.rs new file mode 100644 index 0000000..54532f2 --- /dev/null +++ b/plugins/clerk/src/tools.rs @@ -0,0 +1,376 @@ +use std::collections::HashMap; +use switchboard_guest_sdk::ToolDefinition; + +macro_rules! tool { + ($name:expr, $desc:expr, $params:expr) => { + ToolDefinition { + name: $name.into(), + description: $desc.into(), + parameters: $params, + required: vec![], + } + }; + ($name:expr, $desc:expr, $params:expr, $req:expr) => { + ToolDefinition { + name: $name.into(), + description: $desc.into(), + parameters: $params, + required: $req.iter().map(|s: &&str| s.to_string()).collect(), + } + }; +} + +macro_rules! params { + () => { HashMap::new() }; + ($($k:expr => $v:expr),+ $(,)?) => {{ + let mut m: HashMap = HashMap::new(); + $(m.insert($k.into(), $v.into());)+ + m + }}; +} + +pub fn tool_definitions() -> Vec { + vec![ + // ── Users ──────────────────────────────────────────────────────────── + tool!( + "clerk_list_users", + "Search and list Clerk users by email, phone, username, name, or user ID. Start here for Clerk user management, identity lookup, account audit, finding signed-up users, B2C auth debugging, login history, or banned/locked account review. Covers users, accounts, identities, members, customers.", + params!( + "limit" => "Maximum users to return (default 10, max 500)", + "offset" => "Pagination offset (default 0)", + "order_by" => "Sort field with optional direction, e.g. -created_at, +last_active_at, +last_sign_in_at, +email_address, +username (default -created_at)", + "query" => "Free-text query matched against email, phone, username, first/last name, user ID", + "email_address" => "Comma-separated email addresses to filter by (exact match)", + "phone_number" => "Comma-separated phone numbers to filter by (exact match)", + "username" => "Comma-separated usernames to filter by (exact match)", + "user_id" => "Comma-separated user IDs to filter by", + "external_id" => "Comma-separated external IDs to filter by", + "organization_id" => "Comma-separated org IDs — return users who belong to any of these organizations", + "banned" => "Filter to banned users only (true/false)", + "locked" => "Filter to locked users only (true/false)", + "last_active_at_since" => "Unix epoch ms — return users active since this time" + ) + ), + tool!( + "clerk_get_user", + "Get a Clerk user's full profile, including email addresses, phone numbers, external auth accounts, public/private metadata, and timestamps. Use after list_users when inspecting a specific account.", + params!( + "user_id" => "Clerk user ID (e.g. user_2abc...)" + ), + &["user_id"] + ), + tool!( + "clerk_create_user", + "Create a new Clerk user with email, phone, username, password, name, or external ID. Optionally attach public/private/unsafe metadata.", + params!( + "email_address" => "JSON array string of email addresses, e.g. [\"a@b.com\"]", + "phone_number" => "JSON array string of phone numbers in E.164 format", + "web3_wallet" => "JSON array string of Web3 wallet addresses", + "username" => "Username for the user", + "password" => "Plain-text password (Clerk will hash)", + "password_digest" => "Pre-hashed password digest", + "password_hasher" => "Hash algorithm for password_digest (e.g. bcrypt, argon2i)", + "first_name" => "User's first name", + "last_name" => "User's last name", + "external_id" => "External system ID for this user", + "skip_password_checks" => "Skip password complexity checks (true/false)", + "skip_password_requirement" => "Allow user creation without a password (true/false)", + "public_metadata" => "JSON object string of public metadata", + "private_metadata" => "JSON object string of private metadata", + "unsafe_metadata" => "JSON object string of unsafe metadata", + "created_at" => "Backfill created_at timestamp (RFC3339 or Unix seconds)" + ) + ), + tool!( + "clerk_update_user", + "Update a Clerk user's profile, primary identifier, metadata, password, or activity flags. Use after list_users or get_user.", + params!( + "user_id" => "Clerk user ID", + "first_name" => "Update first name", + "last_name" => "Update last name", + "username" => "Update username", + "primary_email_address_id" => "ID of the email_address record to mark primary", + "primary_phone_number_id" => "ID of the phone_number record to mark primary", + "primary_web3_wallet_id" => "ID of the web3 wallet to mark primary", + "profile_image_id" => "Image ID to use as the profile picture", + "password" => "New password (Clerk will hash)", + "password_digest" => "Pre-hashed password digest", + "password_hasher" => "Hash algorithm for password_digest", + "sign_out_of_other_sessions" => "Sign user out of other active sessions after password change (true/false)", + "external_id" => "External system ID", + "public_metadata" => "JSON object string of public metadata (replaces existing)", + "private_metadata" => "JSON object string of private metadata (replaces existing)", + "unsafe_metadata" => "JSON object string of unsafe metadata (replaces existing)" + ), + &["user_id"] + ), + tool!( + "clerk_delete_user", + "Permanently delete a Clerk user. Use after list_users or get_user when removing an account.", + params!( + "user_id" => "Clerk user ID to delete" + ), + &["user_id"] + ), + tool!( + "clerk_ban_user", + "Ban a Clerk user, preventing all future sign-ins. Reversible via unban_user.", + params!("user_id" => "Clerk user ID to ban"), + &["user_id"] + ), + tool!( + "clerk_unban_user", + "Lift a ban on a Clerk user, restoring their ability to sign in.", + params!("user_id" => "Clerk user ID to unban"), + &["user_id"] + ), + tool!( + "clerk_lock_user", + "Lock a Clerk user out of new sign-ins (without banning). Useful for temporary holds during fraud review.", + params!("user_id" => "Clerk user ID to lock"), + &["user_id"] + ), + tool!( + "clerk_unlock_user", + "Unlock a previously locked Clerk user, restoring sign-in.", + params!("user_id" => "Clerk user ID to unlock"), + &["user_id"] + ), + tool!( + "clerk_list_user_organization_memberships", + "List all Clerk organizations a user belongs to, with their role and organization metadata. Use after list_users to map a user to their tenants/workspaces.", + params!( + "user_id" => "Clerk user ID", + "limit" => "Maximum memberships to return (default 10, max 100)", + "offset" => "Pagination offset" + ), + &["user_id"] + ), + + // ── Sessions ───────────────────────────────────────────────────────── + tool!( + "clerk_list_sessions", + "List Clerk authentication sessions (active sign-ins). Start here for session debugging, auditing who is signed in, revoking suspicious logins, and seeing which clients/devices a user is using.", + params!( + "client_id" => "Filter sessions for a specific client/device", + "user_id" => "Filter sessions for a specific Clerk user", + "status" => "Filter by session status: abandoned, active, ended, expired, removed, replaced, revoked" + ) + ), + tool!( + "clerk_get_session", + "Get a Clerk session by ID, including user, client, status, expiry, and latest activity (browser, device, location). Use after list_sessions when debugging a specific sign-in.", + params!("session_id" => "Clerk session ID (e.g. sess_2abc...)"), + &["session_id"] + ), + tool!( + "clerk_revoke_session", + "Revoke a Clerk session, signing the user out of that client/device. Use after list_sessions or get_session for security incident response.", + params!("session_id" => "Clerk session ID to revoke"), + &["session_id"] + ), + + // ── Organizations ──────────────────────────────────────────────────── + tool!( + "clerk_list_organizations", + "List or search Clerk organizations (tenants, workspaces, teams, accounts). Start here for B2B tenant management, customer audit, finding which organizations exist, or onboarding/billing reviews.", + params!( + "limit" => "Maximum organizations to return (default 10, max 500)", + "offset" => "Pagination offset (default 0)", + "include_members_count" => "Include each organization's member count (true/false)", + "order_by" => "Sort field with optional direction, e.g. -created_at, +name, +members_count (default -created_at)", + "query" => "Free-text query matched against organization name, slug, or ID", + "user_id" => "Comma-separated user IDs — return organizations that any of these users belong to" + ) + ), + tool!( + "clerk_get_organization", + "Get a Clerk organization by ID or slug, including members count, max allowed memberships, creator, metadata, and timestamps. Use after list_organizations.", + params!( + "organization_id" => "Organization ID (e.g. org_2abc...) or slug", + "include_members_count" => "Include the organization's member count (true/false)" + ), + &["organization_id"] + ), + tool!( + "clerk_create_organization", + "Create a new Clerk organization (tenant, workspace, team) with a name, optional slug, creator user, and metadata.", + params!( + "name" => "Organization display name", + "created_by" => "Clerk user ID who will be the initial owner", + "slug" => "URL-safe slug (auto-generated if omitted)", + "max_allowed_memberships" => "Cap on total members (0 or omit for unlimited)", + "public_metadata" => "JSON object string of public metadata", + "private_metadata" => "JSON object string of private metadata" + ), + &["name", "created_by"] + ), + tool!( + "clerk_update_organization", + "Update a Clerk organization's name, slug, member cap, or metadata. Use after list_organizations or get_organization.", + params!( + "organization_id" => "Organization ID or slug", + "name" => "New organization name", + "slug" => "New URL-safe slug", + "max_allowed_memberships" => "New cap on total members (0 for unlimited)", + "admin_delete_enabled" => "Allow admins to delete the organization (true/false)", + "public_metadata" => "JSON object string of public metadata (replaces existing)", + "private_metadata" => "JSON object string of private metadata (replaces existing)" + ), + &["organization_id"] + ), + tool!( + "clerk_delete_organization", + "Permanently delete a Clerk organization. Removes all memberships and invitations.", + params!("organization_id" => "Organization ID or slug to delete"), + &["organization_id"] + ), + tool!( + "clerk_list_organization_memberships", + "List members of a Clerk organization with their role and user data. Use after list_organizations to see who belongs to a tenant/workspace/team.", + params!( + "organization_id" => "Organization ID or slug", + "limit" => "Maximum memberships to return (default 10, max 500)", + "offset" => "Pagination offset", + "order_by" => "Sort field, e.g. -created_at, +last_active_at, +first_name" + ), + &["organization_id"] + ), + tool!( + "clerk_create_organization_membership", + "Add an existing Clerk user to an organization with a role. Use to invite/onboard team members programmatically when they already have an account.", + params!( + "organization_id" => "Organization ID or slug", + "user_id" => "Clerk user ID to add", + "role" => "Role for the new member (e.g. admin, basic_member, or a custom role key)" + ), + &["organization_id", "user_id", "role"] + ), + tool!( + "clerk_update_organization_membership", + "Change a Clerk organization member's role (e.g. promote to admin).", + params!( + "organization_id" => "Organization ID or slug", + "user_id" => "Clerk user ID of the member", + "role" => "New role key" + ), + &["organization_id", "user_id", "role"] + ), + tool!( + "clerk_delete_organization_membership", + "Remove a user from a Clerk organization. Use after list_organization_memberships.", + params!( + "organization_id" => "Organization ID or slug", + "user_id" => "Clerk user ID of the member to remove" + ), + &["organization_id", "user_id"] + ), + tool!( + "clerk_list_organization_invitations", + "List pending, accepted, or revoked invitations to a Clerk organization. Use to audit outstanding invites before resending or revoking.", + params!( + "organization_id" => "Organization ID or slug", + "limit" => "Maximum invitations to return (default 10, max 500)", + "offset" => "Pagination offset", + "status" => "Comma-separated statuses to filter: pending, accepted, revoked" + ), + &["organization_id"] + ), + tool!( + "clerk_create_organization_invitation", + "Invite a user by email to join a Clerk organization with a role. Sends an email and creates a pending invitation.", + params!( + "organization_id" => "Organization ID or slug", + "email_address" => "Recipient email address", + "role" => "Role to grant on acceptance (e.g. admin, basic_member, or a custom role key)", + "inviter_user_id" => "Clerk user ID of the inviter (shown in the invitation email)", + "redirect_url" => "URL to send the recipient to after accepting", + "public_metadata" => "JSON object string of public metadata", + "private_metadata" => "JSON object string of private metadata" + ), + &["organization_id", "email_address", "role"] + ), + tool!( + "clerk_revoke_organization_invitation", + "Revoke a pending Clerk organization invitation. Use after list_organization_invitations.", + params!( + "organization_id" => "Organization ID or slug", + "invitation_id" => "Invitation ID to revoke", + "requesting_user_id" => "Clerk user ID performing the revoke (for audit)" + ), + &["organization_id", "invitation_id"] + ), + + // ── Invitations (instance-level) ───────────────────────────────────── + tool!( + "clerk_list_invitations", + "List Clerk instance-level invitations (not tied to an organization). Start here for auditing outstanding sign-up invites sent to email addresses.", + params!( + "limit" => "Maximum invitations to return (default 10, max 500)", + "offset" => "Pagination offset", + "status" => "Filter by status: pending, accepted, revoked", + "query" => "Free-text query matched against the invitation email address", + "order_by" => "Sort field, e.g. -created_at, -updated_at" + ) + ), + tool!( + "clerk_create_invitation", + "Send a Clerk instance-level invitation to an email address. The recipient gets a sign-up link.", + params!( + "email_address" => "Recipient email address", + "redirect_url" => "URL to send the recipient to after accepting", + "notify" => "Send the invitation email (true/false, default true)", + "ignore_existing" => "Don't error if an invitation already exists for this email (true/false)", + "expires_in_days" => "Invitation lifetime in days", + "template_slug" => "Specific invitation email template slug to use", + "public_metadata" => "JSON object string of public metadata" + ), + &["email_address"] + ), + tool!( + "clerk_revoke_invitation", + "Revoke a pending Clerk instance-level invitation. Use after list_invitations.", + params!("invitation_id" => "Invitation ID to revoke"), + &["invitation_id"] + ), + + // ── Allow / Block list ─────────────────────────────────────────────── + tool!( + "clerk_list_allowlist_identifiers", + "List identifiers (emails, phones, domains) on the Clerk sign-up allow list. Start here when auditing who is allowed to register for the application.", + params!() + ), + tool!( + "clerk_create_allowlist_identifier", + "Add an email address, phone number, or domain (e.g. @example.com) to the Clerk sign-up allow list.", + params!( + "identifier" => "Email address, E.164 phone number, or @domain to allow", + "notify" => "Send a notification to the identifier when added (true/false)" + ), + &["identifier"] + ), + tool!( + "clerk_delete_allowlist_identifier", + "Remove an identifier from the Clerk sign-up allow list.", + params!("identifier_id" => "Allowlist identifier ID returned by list_allowlist_identifiers"), + &["identifier_id"] + ), + tool!( + "clerk_list_blocklist_identifiers", + "List identifiers (emails, phones, domains) on the Clerk sign-up block list. Start here when auditing which addresses are blocked from registering.", + params!() + ), + tool!( + "clerk_create_blocklist_identifier", + "Add an email address, phone number, or domain (e.g. @example.com) to the Clerk sign-up block list.", + params!("identifier" => "Email address, E.164 phone number, or @domain to block"), + &["identifier"] + ), + tool!( + "clerk_delete_blocklist_identifier", + "Remove an identifier from the Clerk sign-up block list.", + params!("identifier_id" => "Blocklist identifier ID returned by list_blocklist_identifiers"), + &["identifier_id"] + ), + ] +} From 72bbcaba3ca802c4db038c97b796428fe51f1c35 Mon Sep 17 00:00:00 2001 From: Dalton Cherry Date: Thu, 21 May 2026 19:19:47 -0500 Subject: [PATCH 2/2] Fix clerk_list_sessions to require user_id or client_id The Clerk API returns 422 if neither filter is provided; surface this as a client-side validation error and clarify the tool description and parameter docs. Co-Authored-By: Claude --- dist/clerk.wasm | Bin 210742 -> 211073 bytes manifest.json | 4 ++-- plugins/clerk/src/handlers.rs | 7 +++++++ plugins/clerk/src/tools.rs | 6 +++--- 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/dist/clerk.wasm b/dist/clerk.wasm index 700f255c2e548f6ad132841bd78ac2509a906436..1ff9adccb6df9309275f5c0b5ebf4c0a7ede2b98 100755 GIT binary patch delta 23915 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 KF&qeS-2XpD!$i0M delta 23409 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�m5=&6JlX(3`=gEks{xLd)<-K#yLZ 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 LNeo24@bCWs0}K;< diff --git a/manifest.json b/manifest.json index e131200..0a44c10 100644 --- a/manifest.json +++ b/manifest.json @@ -34,8 +34,8 @@ "abi_min": 1, "abi_max": 1, "url": "https://raw.githubusercontent.com/daltoniam/switchboard_plugins/main/dist/clerk.wasm", - "sha256": "6527b37ec24e163da5afdd1d6f8a4c7baf9d07988a8721f8eb427feec1f2139d", - "size": 210742, + "sha256": "6e588545f08256ccddfe45df86d22ff656ab549f2d24e201f73c3d11d94540d8", + "size": 211073, "released_at": "2026-05-21T23:42:41Z", "changelog": "Initial release. 34 tools across users, sessions, organizations, memberships, invitations, and allow/block list identifiers." } diff --git a/plugins/clerk/src/handlers.rs b/plugins/clerk/src/handlers.rs index fca76ec..b79aa98 100644 --- a/plugins/clerk/src/handlers.rs +++ b/plugins/clerk/src/handlers.rs @@ -380,6 +380,13 @@ pub fn list_sessions(args: HashMap) -> sdk::ToolResul add_str_param(&args, &mut params, "user_id", "user_id"); add_str_param(&args, &mut params, "status", "status"); + let has_filter = params + .iter() + .any(|(k, _)| k == "client_id" || k == "user_id"); + if !has_filter { + return sdk::err_result("user_id or client_id is required"); + } + let v = call!(clerk_get(&append_query("/sessions", ¶ms))); json_result(&v) } diff --git a/plugins/clerk/src/tools.rs b/plugins/clerk/src/tools.rs index 54532f2..150f334 100644 --- a/plugins/clerk/src/tools.rs +++ b/plugins/clerk/src/tools.rs @@ -150,10 +150,10 @@ pub fn tool_definitions() -> Vec { // ── Sessions ───────────────────────────────────────────────────────── tool!( "clerk_list_sessions", - "List Clerk authentication sessions (active sign-ins). Start here for session debugging, auditing who is signed in, revoking suspicious logins, and seeing which clients/devices a user is using.", + "List Clerk authentication sessions (active sign-ins) for a user or client. Start here for session debugging, auditing who is signed in, revoking suspicious logins, and seeing which clients/devices a user is using. Requires user_id or client_id.", params!( - "client_id" => "Filter sessions for a specific client/device", - "user_id" => "Filter sessions for a specific Clerk user", + "user_id" => "Filter sessions for a specific Clerk user (required if client_id not set)", + "client_id" => "Filter sessions for a specific client/device (required if user_id not set)", "status" => "Filter by session status: abandoned, active, ended, expired, removed, replaced, revoked" ) ),