mirror of
https://github.com/stack-auth/stack.git
synced 2026-06-13 21:01:21 +08:00
<!--
Make sure you've read the CONTRIBUTING.md guidelines:
https://github.com/stack-auth/stack-auth/blob/dev/CONTRIBUTING.md
-->
<!-- ELLIPSIS_HIDDEN -->
----
> [!IMPORTANT]
> This pull request updates the Stack Auth documentation structure,
enhances navigation and layout functionalities, and introduces new
components for improved user experience.
>
> - **Behavior**:
> - Introduces `PlatformRedirect` component in `platform-redirect.tsx`
for redirecting users to their preferred platform.
> - Adds `usePlatformPreference` hook in `use-platform-preference.ts`
for managing platform preferences.
> - Updates `getSmartRedirectUrl()` in `navigation-utils.ts` to use
`getSmartPlatformRedirect()`.
> - **Layout and Navigation**:
> - Enhances sidebar functionality with collapsible sections in
`docs.tsx` and `sidebar-context.tsx`.
> - Adds `DocsSidebarCollapseTrigger` in `docs.tsx` for sidebar
collapse/expand functionality.
> - Updates `SharedHeader` in `shared-header.tsx` to include
platform-aware navigation links.
> - **Documentation Structure**:
> - Updates `meta.json` files in `templates` to reflect new
documentation structure.
> - Renames `overview.mdx` to `index.mdx` in `sdk` and `components`
directories.
> - Adds detailed documentation for `Team`, `TeamUser`, and
`ContactChannel` in respective `.mdx` files.
>
> <sup>This description was created by </sup>[<img alt="Ellipsis"
src="https://img.shields.io/badge/Ellipsis-blue?color=175173">](https://www.ellipsis.dev?ref=stack-auth%2Fstack-auth&utm_source=github&utm_medium=referral)<sup>
for 21e55737cb. You can
[customize](https://app.ellipsis.dev/stack-auth/settings/summaries) this
summary. It will automatically update as commits are pushed.</sup>
<!-- ELLIPSIS_HIDDEN -->
---------
Co-authored-by: Stack-Bot <madison@stack-auth.com>
Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
Co-authored-by: Konsti Wohlwend <n2d4xc@gmail.com>
600 lines
17 KiB
Plaintext
600 lines
17 KiB
Plaintext
---
|
|
title: "Teams Management"
|
|
description: "Learn how to implement team functionality in your Python application using Stack Auth's REST API"
|
|
---
|
|
|
|
After setting up your [Stack Auth helper function](../getting-started/setup.mdx), you can implement comprehensive team functionality in your Python application.
|
|
|
|
## Team Management
|
|
|
|
Stack Auth provides full team management capabilities including:
|
|
- **Team Creation & Management** - Create and update teams with metadata
|
|
- **Team Memberships** - Add and remove users from teams
|
|
- **Team Invitations** - Send email invitations to join teams
|
|
- **Team Permissions** - Control what team members can do
|
|
- **Team Profiles** - Manage user profiles within team context
|
|
|
|
### Creating a Team
|
|
|
|
To create a new team:
|
|
|
|
```python
|
|
def create_team(access_token, display_name, creator_user_id=None):
|
|
"""
|
|
Create a new team
|
|
Returns the created team data
|
|
"""
|
|
body = {
|
|
'display_name': display_name
|
|
}
|
|
|
|
# Optionally specify a creator (only on server)
|
|
if creator_user_id:
|
|
body['creator_user_id'] = creator_user_id
|
|
|
|
response = stack_auth_request('POST', 'api/v1/teams',
|
|
headers={'x-stack-access-token': access_token},
|
|
json=body
|
|
)
|
|
|
|
return {
|
|
'id': response['id'],
|
|
'display_name': response['display_name'],
|
|
'profile_image_url': response['profile_image_url'],
|
|
'client_metadata': response['client_metadata'],
|
|
'client_read_only_metadata': response['client_read_only_metadata'],
|
|
'created_at_millis': response.get('created_at_millis') # Server only
|
|
}
|
|
|
|
# Example usage
|
|
team_data = create_team(
|
|
access_token=access_token,
|
|
display_name="Engineering Team"
|
|
)
|
|
team_id = team_data['id']
|
|
print(f"Created team: {team_data['display_name']}")
|
|
```
|
|
|
|
### Listing Teams
|
|
|
|
Get all teams for the current user:
|
|
|
|
```python
|
|
def list_user_teams(access_token):
|
|
"""
|
|
List all teams that the current user is a member of
|
|
"""
|
|
response = stack_auth_request('GET', 'api/v1/teams?user_id=me',
|
|
headers={'x-stack-access-token': access_token}
|
|
)
|
|
|
|
return response['items']
|
|
|
|
def list_all_teams():
|
|
"""
|
|
List all teams in the project (server access only)
|
|
"""
|
|
response = stack_auth_request('GET', 'api/v1/teams')
|
|
return response['items']
|
|
|
|
# Example usage
|
|
user_teams = list_user_teams(access_token)
|
|
print(f"User is member of {len(user_teams)} teams")
|
|
|
|
# Server-side: list all teams
|
|
all_teams = list_all_teams()
|
|
print(f"Total teams in project: {len(all_teams)}")
|
|
```
|
|
|
|
### Getting Team Information
|
|
|
|
Retrieve details about a specific team:
|
|
|
|
```python
|
|
def get_team(access_token, team_id):
|
|
"""
|
|
Get information about a specific team
|
|
"""
|
|
response = stack_auth_request('GET', f'api/v1/teams/{team_id}',
|
|
headers={'x-stack-access-token': access_token}
|
|
)
|
|
|
|
return {
|
|
'id': response['id'],
|
|
'display_name': response['display_name'],
|
|
'profile_image_url': response['profile_image_url'],
|
|
'client_metadata': response['client_metadata'],
|
|
'client_read_only_metadata': response['client_read_only_metadata']
|
|
}
|
|
|
|
# Example usage
|
|
team_info = get_team(access_token, team_id)
|
|
print(f"Team: {team_info['display_name']}")
|
|
```
|
|
|
|
### Updating Team Information
|
|
|
|
Update team details (requires `$update_team` permission):
|
|
|
|
```python
|
|
def update_team(access_token, team_id, **updates):
|
|
"""
|
|
Update team information
|
|
Requires $update_team permission
|
|
"""
|
|
# Filter out None values
|
|
body = {k: v for k, v in updates.items() if v is not None}
|
|
|
|
response = stack_auth_request('PATCH', f'api/v1/teams/{team_id}',
|
|
headers={'x-stack-access-token': access_token},
|
|
json=body
|
|
)
|
|
|
|
return response
|
|
|
|
# Example usage
|
|
updated_team = update_team(
|
|
access_token=access_token,
|
|
team_id=team_id,
|
|
display_name="Updated Engineering Team",
|
|
profile_image_url="https://example.com/team-logo.png",
|
|
client_metadata={
|
|
"department": "Engineering",
|
|
"location": "San Francisco"
|
|
}
|
|
)
|
|
```
|
|
|
|
## Team Membership Management
|
|
|
|
### Adding Members to a Team
|
|
|
|
Add users to a team (server access required):
|
|
|
|
```python
|
|
def add_team_member(team_id, user_id):
|
|
"""
|
|
Add a user to a team (server access only)
|
|
"""
|
|
response = stack_auth_request('POST', f'api/v1/team-memberships/{team_id}/{user_id}',
|
|
json={}
|
|
)
|
|
return response
|
|
|
|
# Example usage
|
|
add_team_member(team_id, user_id)
|
|
print(f"Added user {user_id} to team {team_id}")
|
|
```
|
|
|
|
### Removing Members from a Team
|
|
|
|
Remove users from a team (requires `$remove_members` permission):
|
|
|
|
```python
|
|
def remove_team_member(access_token, team_id, user_id):
|
|
"""
|
|
Remove a user from a team
|
|
Requires $remove_members permission
|
|
"""
|
|
stack_auth_request('DELETE', f'api/v1/team-memberships/{team_id}/{user_id}',
|
|
headers={'x-stack-access-token': access_token}
|
|
)
|
|
|
|
# Example usage
|
|
remove_team_member(access_token, team_id, user_id)
|
|
print(f"Removed user {user_id} from team {team_id}")
|
|
```
|
|
|
|
### Getting Team Members
|
|
|
|
List all members of a team:
|
|
|
|
```python
|
|
def get_team_members(access_token, team_id):
|
|
"""
|
|
Get all members of a team with their profiles
|
|
Requires $read_members permission
|
|
"""
|
|
response = stack_auth_request('GET', f'api/v1/team-member-profiles?team_id={team_id}',
|
|
headers={'x-stack-access-token': access_token}
|
|
)
|
|
|
|
return response['items']
|
|
|
|
# Example usage
|
|
members = get_team_members(access_token, team_id)
|
|
for member in members:
|
|
print(f"Member: {member['display_name']} ({member['user_id']})")
|
|
```
|
|
|
|
## Team Invitations
|
|
|
|
### Sending Team Invitations
|
|
|
|
Invite users to join a team via email:
|
|
|
|
```python
|
|
def send_team_invitation(access_token, team_id, email, callback_url):
|
|
"""
|
|
Send an invitation to join a team
|
|
Requires $invite_members permission
|
|
"""
|
|
response = stack_auth_request('POST', 'api/v1/team-invitations/send-code',
|
|
headers={'x-stack-access-token': access_token},
|
|
json={
|
|
'email': email,
|
|
'team_id': team_id,
|
|
'callback_url': callback_url
|
|
}
|
|
)
|
|
|
|
return {
|
|
'success': response['success'],
|
|
'invitation_id': response['id']
|
|
}
|
|
|
|
# Example usage
|
|
invitation_result = send_team_invitation(
|
|
access_token=access_token,
|
|
team_id=team_id,
|
|
email="newmember@example.com",
|
|
callback_url="https://yourapp.com/join-team"
|
|
)
|
|
print(f"Invitation sent: {invitation_result['invitation_id']}")
|
|
```
|
|
|
|
### Accepting Team Invitations
|
|
|
|
Complete the invitation process when a user clicks the invitation link:
|
|
|
|
```python
|
|
def accept_team_invitation(code):
|
|
"""
|
|
Accept a team invitation using the code from the invitation email
|
|
"""
|
|
response = stack_auth_request('POST', 'api/v1/team-invitations/accept',
|
|
json={'code': code}
|
|
)
|
|
|
|
return response
|
|
|
|
# Example usage (when user clicks invitation link)
|
|
accept_team_invitation(invitation_code)
|
|
print("User successfully joined the team!")
|
|
```
|
|
|
|
### Listing Team Invitations
|
|
|
|
Get pending invitations for a team:
|
|
|
|
```python
|
|
def list_team_invitations(access_token, team_id):
|
|
"""
|
|
List pending invitations for a team
|
|
Requires $invite_members permission
|
|
"""
|
|
response = stack_auth_request('GET', f'api/v1/team-invitations?team_id={team_id}',
|
|
headers={'x-stack-access-token': access_token}
|
|
)
|
|
|
|
return response['items']
|
|
|
|
# Example usage
|
|
invitations = list_team_invitations(access_token, team_id)
|
|
for invitation in invitations:
|
|
print(f"Pending invitation for: {invitation['recipient_email']}")
|
|
```
|
|
|
|
## Team Permissions Management
|
|
|
|
### Granting Team Permissions
|
|
|
|
Give specific permissions to team members:
|
|
|
|
```python
|
|
def grant_team_permission(team_id, user_id, permission_id):
|
|
"""
|
|
Grant a permission to a user in a team (server access only)
|
|
"""
|
|
response = stack_auth_request('POST', f'api/v1/team-permissions/{team_id}/{user_id}/{permission_id}',
|
|
json={}
|
|
)
|
|
return response
|
|
|
|
# Example usage
|
|
grant_team_permission(team_id, user_id, "$update_team")
|
|
grant_team_permission(team_id, user_id, "$invite_members")
|
|
print(f"Granted permissions to user {user_id}")
|
|
```
|
|
|
|
### Revoking Team Permissions
|
|
|
|
Remove permissions from team members:
|
|
|
|
```python
|
|
def revoke_team_permission(team_id, user_id, permission_id):
|
|
"""
|
|
Revoke a permission from a user in a team (server access only)
|
|
"""
|
|
stack_auth_request('DELETE', f'api/v1/team-permissions/{team_id}/{user_id}/{permission_id}')
|
|
|
|
# Example usage
|
|
revoke_team_permission(team_id, user_id, "$update_team")
|
|
print(f"Revoked permission from user {user_id}")
|
|
```
|
|
|
|
### Checking Team Permissions
|
|
|
|
Check if a user has specific permissions in a team:
|
|
|
|
```python
|
|
def check_team_permission(access_token, team_id, user_id, permission_id):
|
|
"""
|
|
Check if a user has a specific permission in a team
|
|
"""
|
|
try:
|
|
response = stack_auth_request('GET', f'api/v1/team-permissions/{team_id}/{user_id}/{permission_id}',
|
|
headers={'x-stack-access-token': access_token}
|
|
)
|
|
return True
|
|
except Exception as e:
|
|
if "TEAM_PERMISSION_NOT_FOUND" in str(e):
|
|
return False
|
|
raise e
|
|
|
|
# Example usage
|
|
can_update = check_team_permission(access_token, team_id, user_id, "$update_team")
|
|
if can_update:
|
|
print("User can update the team")
|
|
else:
|
|
print("User cannot update the team")
|
|
```
|
|
|
|
### Listing User Permissions
|
|
|
|
Get all permissions a user has in a team:
|
|
|
|
```python
|
|
def list_user_team_permissions(access_token, team_id, user_id="me"):
|
|
"""
|
|
List all permissions a user has in a team
|
|
"""
|
|
response = stack_auth_request('GET', f'api/v1/team-permissions/{team_id}/{user_id}',
|
|
headers={'x-stack-access-token': access_token}
|
|
)
|
|
|
|
return response['items']
|
|
|
|
# Example usage
|
|
permissions = list_user_team_permissions(access_token, team_id)
|
|
permission_ids = [p['id'] for p in permissions]
|
|
print(f"User permissions: {permission_ids}")
|
|
```
|
|
|
|
## Team Member Profiles
|
|
|
|
### Managing Team Member Profiles
|
|
|
|
Users can have different display names and profile information within each team:
|
|
|
|
```python
|
|
def update_team_member_profile(access_token, team_id, user_id="me", **profile_data):
|
|
"""
|
|
Update a user's profile within a team context
|
|
"""
|
|
response = stack_auth_request('PATCH', f'api/v1/team-member-profiles/{team_id}/{user_id}',
|
|
headers={'x-stack-access-token': access_token},
|
|
json=profile_data
|
|
)
|
|
|
|
return response
|
|
|
|
def get_team_member_profile(access_token, team_id, user_id="me"):
|
|
"""
|
|
Get a user's profile within a team context
|
|
"""
|
|
response = stack_auth_request('GET', f'api/v1/team-member-profiles/{team_id}/{user_id}',
|
|
headers={'x-stack-access-token': access_token}
|
|
)
|
|
|
|
return response
|
|
|
|
# Example usage
|
|
# Update current user's profile in the team
|
|
updated_profile = update_team_member_profile(
|
|
access_token=access_token,
|
|
team_id=team_id,
|
|
display_name="John Doe (Engineering Lead)",
|
|
profile_image_url="https://example.com/john-avatar.png"
|
|
)
|
|
|
|
# Get the updated profile
|
|
profile = get_team_member_profile(access_token, team_id)
|
|
print(f"Team profile: {profile['display_name']}")
|
|
```
|
|
|
|
### Deleting Teams
|
|
|
|
Remove a team entirely (requires `$delete_team` permission):
|
|
|
|
```python
|
|
def delete_team(access_token, team_id):
|
|
"""
|
|
Delete a team (requires $delete_team permission)
|
|
"""
|
|
response = stack_auth_request('DELETE', f'api/v1/teams/{team_id}',
|
|
headers={'x-stack-access-token': access_token}
|
|
)
|
|
return response
|
|
|
|
# Example usage
|
|
delete_team(access_token, team_id)
|
|
print(f"Team {team_id} deleted successfully")
|
|
```
|
|
|
|
## Complete Team Management Example
|
|
|
|
Here's a comprehensive example that demonstrates a full team management workflow:
|
|
|
|
```python
|
|
import os
|
|
import requests
|
|
|
|
# Setup (from setup guide)
|
|
stack_project_id = os.getenv("STACK_PROJECT_ID")
|
|
stack_publishable_client_key = os.getenv("STACK_PUBLISHABLE_CLIENT_KEY")
|
|
stack_secret_server_key = os.getenv("STACK_SECRET_SERVER_KEY")
|
|
|
|
def stack_auth_request(method, endpoint, **kwargs):
|
|
res = requests.request(
|
|
method,
|
|
f'https://api.stack-auth.com/{endpoint}',
|
|
headers={
|
|
'x-stack-access-type': 'server', # or 'client' if you're only accessing the client API
|
|
'x-stack-project-id': stack_project_id,
|
|
'x-stack-publishable-client-key': stack_publishable_client_key,
|
|
'x-stack-secret-server-key': stack_secret_server_key, # not necessary if access type is 'client'
|
|
**kwargs.pop('headers', {}),
|
|
},
|
|
**kwargs,
|
|
)
|
|
if res.status_code >= 400:
|
|
raise Exception(f"Stack Auth API request failed with {res.status_code}: {res.text}")
|
|
return res.json()
|
|
|
|
class TeamManager:
|
|
def __init__(self, access_token):
|
|
self.access_token = access_token
|
|
|
|
def create_and_setup_team(self, name, member_emails):
|
|
"""Create a team and invite members"""
|
|
# Create the team
|
|
team = create_team(self.access_token, name)
|
|
team_id = team['id']
|
|
|
|
print(f"Created team: {name} (ID: {team_id})")
|
|
|
|
# Send invitations to members
|
|
invitation_results = []
|
|
for email in member_emails:
|
|
try:
|
|
result = send_team_invitation(
|
|
self.access_token,
|
|
team_id,
|
|
email,
|
|
"https://yourapp.com/join-team"
|
|
)
|
|
invitation_results.append((email, result['invitation_id']))
|
|
print(f"Invited {email}")
|
|
except Exception as e:
|
|
print(f"Failed to invite {email}: {e}")
|
|
|
|
return team, invitation_results
|
|
|
|
def manage_team_permissions(self, team_id, admin_user_ids):
|
|
"""Grant admin permissions to specific users"""
|
|
admin_permissions = ["$update_team", "$invite_members", "$remove_members"]
|
|
|
|
for user_id in admin_user_ids:
|
|
for permission in admin_permissions:
|
|
try:
|
|
grant_team_permission(team_id, user_id, permission)
|
|
print(f"Granted {permission} to {user_id}")
|
|
except Exception as e:
|
|
print(f"Failed to grant {permission} to {user_id}: {e}")
|
|
|
|
def get_team_overview(self, team_id):
|
|
"""Get complete team information"""
|
|
# Get team details
|
|
team_info = get_team(self.access_token, team_id)
|
|
|
|
# Get team members
|
|
try:
|
|
members = get_team_members(self.access_token, team_id)
|
|
except Exception:
|
|
members = [] # User might not have read_members permission
|
|
|
|
# Get pending invitations
|
|
try:
|
|
invitations = list_team_invitations(self.access_token, team_id)
|
|
except Exception:
|
|
invitations = [] # User might not have invite_members permission
|
|
|
|
return {
|
|
'team': team_info,
|
|
'members': members,
|
|
'pending_invitations': invitations
|
|
}
|
|
|
|
# Example usage
|
|
team_manager = TeamManager(access_token)
|
|
|
|
# Create a new team with initial members
|
|
team, invitations = team_manager.create_and_setup_team(
|
|
name="Product Team",
|
|
member_emails=["alice@example.com", "bob@example.com", "charlie@example.com"]
|
|
)
|
|
|
|
# Make some users team admins (server-side operation)
|
|
admin_users = ["user-id-1", "user-id-2"]
|
|
team_manager.manage_team_permissions(team['id'], admin_users)
|
|
|
|
# Get team overview
|
|
overview = team_manager.get_team_overview(team['id'])
|
|
print(f"\nTeam Overview:")
|
|
print(f"Name: {overview['team']['display_name']}")
|
|
print(f"Members: {len(overview['members'])}")
|
|
print(f"Pending Invitations: {len(overview['pending_invitations'])}")
|
|
```
|
|
|
|
## Error Handling
|
|
|
|
Common team-related errors you might encounter:
|
|
|
|
```python
|
|
def handle_team_errors(func):
|
|
"""Decorator to handle common team operation errors"""
|
|
def wrapper(*args, **kwargs):
|
|
try:
|
|
return func(*args, **kwargs)
|
|
except Exception as e:
|
|
error_message = str(e)
|
|
|
|
if "TEAM_PERMISSION_REQUIRED" in error_message:
|
|
print("Insufficient permissions for this team operation")
|
|
elif "TEAM_MEMBERSHIP_NOT_FOUND" in error_message:
|
|
print("User is not a member of this team")
|
|
elif "TEAM_NOT_FOUND" in error_message:
|
|
print("Team not found")
|
|
elif "TEAM_MEMBERSHIP_ALREADY_EXISTS" in error_message:
|
|
print("User is already a member of this team")
|
|
elif "USER_NOT_FOUND" in error_message:
|
|
print("User not found")
|
|
else:
|
|
print(f"Team operation error: {error_message}")
|
|
|
|
raise e
|
|
return wrapper
|
|
|
|
@handle_team_errors
|
|
def safe_add_member(team_id, user_id):
|
|
return add_team_member(team_id, user_id)
|
|
|
|
@handle_team_errors
|
|
def safe_send_invitation(access_token, team_id, email, callback_url):
|
|
return send_team_invitation(access_token, team_id, email, callback_url)
|
|
```
|
|
|
|
## Common Team Permission IDs
|
|
|
|
Stack Auth includes several built-in team permissions:
|
|
|
|
- **`$update_team`** - Edit team information and metadata
|
|
- **`$delete_team`** - Delete the entire team
|
|
- **`$invite_members`** - Send invitations to new members
|
|
- **`$remove_members`** - Remove members from the team
|
|
- **`$read_members`** - View team member list
|
|
- **`team_member`** - Basic team membership (automatically granted)
|
|
|
|
For more advanced team features, check out the [REST API documentation](../rest-api/overview.mdx).
|
|
|