Skip to main content

04_simple_multi_tenant/
04_simple_multi_tenant.rs

1//! Simple Multi-Tenant Setup
2//!
3//! This example demonstrates:
4//! - Creating multiple tenants
5//! - Creating users within specific tenants
6//! - Tenant isolation (resources belong to specific tenants)
7//!
8//! Scenario: Two companies (CompanyA and CompanyB) each with their own users.
9//!
10//! Run with: `cargo run --example 04_simple_multi_tenant`
11
12use std::sync::Arc;
13use tokio::sync::RwLock;
14use wami::arn::{TenantPath, WamiArn};
15use wami::context::WamiContext;
16use wami::service::{TenantService, UserService};
17use wami::store::memory::InMemoryWamiStore;
18use wami::wami::identity::user::requests::{CreateUserRequest, ListUsersRequest};
19
20#[tokio::main]
21async fn main() -> Result<(), Box<dyn std::error::Error>> {
22    println!("=== Simple Multi-Tenant Setup ===\n");
23
24    let store = Arc::new(RwLock::new(InMemoryWamiStore::default()));
25
26    // Create root context
27    let root_context = WamiContext::builder()
28        .instance_id("123456789012")
29        .tenant_path(TenantPath::single(0)) // Root tenant ID is 0
30        .caller_arn(
31            WamiArn::builder()
32                .service(wami::arn::Service::Iam)
33                .tenant_path(TenantPath::single(0))
34                .wami_instance("123456789012")
35                .resource("user", "admin")
36                .build()?,
37        )
38        .is_root(true)
39        .build()?;
40
41    // === CREATE TENANTS ===
42    println!("Step 1: Creating tenants...\n");
43
44    let tenant_service = TenantService::new(store.clone());
45
46    // Create Company A tenant (service generates numeric ID automatically)
47    let company_a = tenant_service
48        .create_tenant(
49            &root_context,
50            "company-a".to_string(),
51            Some("Company A Corp".to_string()),
52            None, // No parent, this is a root tenant
53        )
54        .await?;
55    let company_a_id = company_a.id.clone();
56    println!("✓ Created tenant: company-a (ID: {})", company_a.id);
57
58    // Create Company B tenant (service generates numeric ID automatically)
59    let company_b = tenant_service
60        .create_tenant(
61            &root_context,
62            "company-b".to_string(),
63            Some("Company B Inc".to_string()),
64            None,
65        )
66        .await?;
67    let company_b_id = company_b.id.clone();
68    println!("✓ Created tenant: company-b (ID: {})", company_b.id);
69
70    // === CREATE USERS IN EACH TENANT ===
71    println!("\nStep 2: Creating users in each tenant...\n");
72
73    // Get numeric tenant IDs for contexts (first segment is the root tenant ID)
74    let company_a_tenant_id = company_a_id.segments()[0];
75    let company_b_tenant_id = company_b_id.segments()[0];
76
77    // Company A context
78    let company_a_context = WamiContext::builder()
79        .instance_id("123456789012")
80        .tenant_path(TenantPath::single(company_a_tenant_id))
81        .caller_arn(
82            WamiArn::builder()
83                .service(wami::arn::Service::Iam)
84                .tenant_path(TenantPath::single(company_a_tenant_id))
85                .wami_instance("123456789012")
86                .resource("user", "admin")
87                .build()?,
88        )
89        .is_root(false)
90        .build()?;
91
92    // Company B context
93    let company_b_context = WamiContext::builder()
94        .instance_id("123456789012")
95        .tenant_path(TenantPath::single(company_b_tenant_id))
96        .caller_arn(
97            WamiArn::builder()
98                .service(wami::arn::Service::Iam)
99                .tenant_path(TenantPath::single(company_b_tenant_id))
100                .wami_instance("123456789012")
101                .resource("user", "admin")
102                .build()?,
103        )
104        .is_root(false)
105        .build()?;
106
107    // Company A users
108    println!("Creating users for Company A...");
109    let user_service = UserService::new(store.clone());
110
111    let alice_req = CreateUserRequest {
112        user_name: "alice".to_string(),
113        path: Some("/company-a/".to_string()),
114        permissions_boundary: None,
115        tags: None,
116    };
117    let alice = user_service
118        .create_user(&company_a_context, alice_req)
119        .await?;
120    println!("✓ Created alice in company-a");
121    println!("  ARN: {}", alice.arn);
122
123    let bob_req = CreateUserRequest {
124        user_name: "bob".to_string(),
125        path: Some("/company-a/".to_string()),
126        permissions_boundary: None,
127        tags: None,
128    };
129    user_service
130        .create_user(&company_a_context, bob_req)
131        .await?;
132    println!("✓ Created bob in company-a");
133
134    // Company B users
135    println!("\nCreating users for Company B...");
136
137    let charlie_req = CreateUserRequest {
138        user_name: "charlie".to_string(),
139        path: Some("/company-b/".to_string()),
140        permissions_boundary: None,
141        tags: None,
142    };
143    let charlie = user_service
144        .create_user(&company_b_context, charlie_req)
145        .await?;
146    println!("✓ Created charlie in company-b");
147    println!("  ARN: {}", charlie.arn);
148
149    let diana_req = CreateUserRequest {
150        user_name: "diana".to_string(),
151        path: Some("/company-b/".to_string()),
152        permissions_boundary: None,
153        tags: None,
154    };
155    user_service
156        .create_user(&company_b_context, diana_req)
157        .await?;
158    println!("✓ Created diana in company-b");
159
160    // === DEMONSTRATE ISOLATION ===
161    println!("\n\nStep 3: Demonstrating tenant isolation...\n");
162
163    // List all users (cross-tenant view - usually restricted in production)
164    let (all_users, _, _) = user_service
165        .list_users(
166            &root_context,
167            ListUsersRequest {
168                path_prefix: None,
169                pagination: None,
170            },
171        )
172        .await?;
173    println!("Total users across all tenants: {}", all_users.len());
174
175    // Company A can only see its users (using company-a context)
176    let (company_a_users, _, _) = user_service
177        .list_users(
178            &company_a_context,
179            ListUsersRequest {
180                path_prefix: Some("/company-a/".to_string()),
181                pagination: None,
182            },
183        )
184        .await?;
185    println!(
186        "\nCompany A users (filtered by path): {}",
187        company_a_users.len()
188    );
189    for user in &company_a_users {
190        println!("  - {} (path: {})", user.user_name, user.path);
191    }
192
193    // Company B can only see its users (using company-b context)
194    let (company_b_users, _, _) = user_service
195        .list_users(
196            &company_b_context,
197            ListUsersRequest {
198                path_prefix: Some("/company-b/".to_string()),
199                pagination: None,
200            },
201        )
202        .await?;
203    println!(
204        "\nCompany B users (filtered by path): {}",
205        company_b_users.len()
206    );
207    for user in &company_b_users {
208        println!("  - {} (path: {})", user.user_name, user.path);
209    }
210
211    // List all tenants
212    println!("\n\nStep 4: Listing all tenants...\n");
213    let all_tenants = tenant_service.list_tenants().await?;
214    println!("✓ Found {} tenants:", all_tenants.len());
215    for tenant in &all_tenants {
216        println!("  - {} ({})", tenant.name, tenant.id);
217    }
218
219    println!("\n✅ Example completed successfully!");
220    println!("Key takeaways:");
221    println!("- Each tenant has its own namespace for resources");
222    println!("- WamiContext is used to scope operations to specific tenants");
223    println!("- ARNs reflect the tenant ownership via tenant_path");
224    println!("- Tenant isolation ensures data security in multi-tenant apps");
225
226    Ok(())
227}