Implemented Teams plan features for BurnLens Cloud backend, enabling multi-user workspace collaboration with role-based access control (RBAC), invitation system, and activity logging.
-
4 new PostgreSQL tables:
users— Email + OAuth identifiers (google_id, github_id)workspace_members— Membership tracking with roles (owner/admin/viewer)invitations— Pending invites with 48-hour expiry tokensworkspace_activity— Audit log of admin actions
-
Model Updates:
- Extended
TokenPayloadwithuser_idandrolefields - Added Pydantic schemas for team endpoints (InvitationRequest, InvitationResponse, etc.)
- Extended
-
Configuration:
- Seat limits per plan: free=1, cloud=3, teams=10, enterprise=999
- SSO environment variables (Google, GitHub)
- Email configuration (SendGrid)
- Invitation expiry: 48 hours
-
Email Service:
- Created
email.pywith SendGrid integration - Async non-blocking invitation email sending
- Created
-
Team Management API (
team_api.py):GET /team/members— List workspace membersDELETE /team/members/{user_id}— Remove member (admin+)PATCH /team/members/{user_id}— Change role (admin+)GET /team/activity— Audit log (admin+)
-
Authentication Enhancements (auth.py):
upsert_user()— Create or update user by email/OAuth IDsensure_workspace_member()— Add user to workspace with roleauto_migrate_user_for_workspace()— Transparent migration for existing workspaces- Updated
encode_jwt()to include user_id and role - Updated login/signup flows to create user records
-
POST /team/invite — Send invitations (admin+ required)
- Validates team plan requirement
- Enforces seat limits
- Sends email with invitation link
- Logs activity
-
GET /invite/{token} — Accept invitations
- Handles unauthenticated users (redirects to signup)
- Validates token expiry (48 hours)
- Auto-adds user to workspace_members
- Logs activity
-
Role Hierarchy: viewer (0) < admin (1) < owner (2)
-
Permissions:
- viewer: GET dashboard endpoints only
- admin: GET + settings/budget configuration
- owner: All + billing + workspace deletion
-
Dashboard API Updates (dashboard_api.py):
- Added
require_role()helper for permission checking - Updated all GET endpoints to require "viewer" role
- Returns 403 with error details on insufficient permissions
- Added
- Existing single-user workspaces auto-migrate on first login
- Creates user record for owner_email
- Adds to workspace_members with "owner" role
- Transparent to existing users
- Created
test_teams.pywith comprehensive test suite:- Invitation creation and validation
- Seat limit enforcement
- Role-based access control
- Member management
- Activity logging
burnlens_cloud/team_api.py— Team management API (330 lines)burnlens_cloud/email.py— Email service (85 lines)tests/test_teams.py— Team functionality tests (250 lines)
burnlens_cloud/database.py— Added 4 table schemas + indexesburnlens_cloud/models.py— Extended TokenPayload, added team modelsburnlens_cloud/config.py— Added seat limits, SSO vars, email configburnlens_cloud/auth.py— User/member management, auto-migration, updated JWT encodingburnlens_cloud/dashboard_api.py— Added RBAC enforcementburnlens_cloud/main.py— Registered team_api routerpyproject.toml— Added sendgrid, google-auth-oauthlib, requests dependencies
- Email-based invitations with 48-hour expiry
- Secure token-based acceptance flow
- Automatic user creation on signup via invite link
- Activity logging for all invitations
- 3-tier role system: owner, admin, viewer
- Transparent enforcement on dashboard endpoints
- Prevents unauthorized access with 403 errors
- Prevents removal of last owner
- Enforced during invitation creation
- Plan-specific limits (free=1, teams=10)
- Returns 422 error with upgrade prompt when exceeded
- Existing workspaces transparently add user records
- Owner email becomes the first user
- Happens on first login (API key login)
- No manual migration script needed
- All admin actions logged to
workspace_activitytable - Actions tracked: invite_sent, member_joined, member_removed, role_changed
- Available via
/team/activityendpoint (admin+ only)
OAuth flows for seamless onboarding are designed but not yet implemented. They can be added in a future release:
GET /auth/google— Google OAuth redirectGET /auth/google/callback— Google OAuth callbackGET /auth/github— GitHub OAuth redirectGET /auth/github/callback— GitHub OAuth callback
See /Users/bhushan/.claude/plans/hazy-watching-lantern.md for SSO implementation details.
# Email (SendGrid)
SENDGRID_API_KEY=...
SENDGRID_FROM_EMAIL=noreply@burnlens.app
BURNLENS_FRONTEND_URL=https://burnlens.app
# Optional (for future SSO):
GOOGLE_CLIENT_ID=...
GOOGLE_CLIENT_SECRET=...
GOOGLE_REDIRECT_URI=https://api.burnlens.app/auth/google/callback
GITHUB_CLIENT_ID=...
GITHUB_CLIENT_SECRET=...
GITHUB_REDIRECT_URI=https://api.burnlens.app/auth/github/callback
All new tables follow PostgreSQL best practices:
- UUID primary keys with
gen_random_uuid() - Referential integrity with
ON DELETE CASCADE - Appropriate indexes for query performance
- JSONB for flexible audit log details
- Timestamptz for UTC time handling
All team endpoints require JWT token in Authorization header:
Authorization: Bearer {jwt_token}
Insufficient role returns 403:
{
"detail": {
"error": "insufficient_role",
"required": "admin",
"current": "viewer"
}
}Exceeding seat limit returns 422:
{
"detail": {
"error": "seat_limit_reached",
"limit": 10,
"upgrade_url": "https://burnlens.app/upgrade"
}
}- Deploy to Staging: Test the Teams flow end-to-end
- Frontend Integration: Create /team dashboard UI for member management
- SSO Implementation: Add Google/GitHub OAuth flows (Phase 4)
- Email Templates: Customize invitation emails with branding
- Activity Dashboard: Build audit log UI for compliance/transparency
- Performance: Monitor database performance and add caching if needed
Run teams tests:
pytest tests/test_teams.py -vAll tests use mocked database operations (no real DB required).
- All database operations are asynchronous (asyncpg)
- Email sending is non-blocking (fire-and-forget)
- Invitation tokens are 32-char hex strings (same format as API keys)
- Activity logging failures don't fail requests (resilient design)
- Viewer role prevents all sensitive operations while allowing full read access