File size: 7,823 Bytes
391c43e | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 | # Multitenancy
OSW Studio server mode supports workspaces and multiple users on a single instance. Workspaces are the primary unit of organization and isolation. Users are granted access to workspaces with roles.
## Concepts
### Workspaces
A workspace is a self-contained environment with its own projects, deployments, templates, skills, and quotas. Each workspace maps to its own SQLite database at `data/workspaces/{id}/osws.sqlite`. Data in one workspace is completely isolated from other workspaces.
Workspaces are what you manage. Users are just accounts that get access to workspaces.
### Access Model
There are two levels of access:
- **Instance admin** -- can create and manage workspaces, users, and instance settings via `/admin/users` and `/admin/workspaces`
- **Workspace member** -- can use the AI, edit projects, publish sites, and manage deployments within workspaces they have access to
All workspace members have the same capabilities within a workspace.
### How it fits together
An agency running OSWS might set up:
- A workspace per client (e.g., "Sweet Candies", "Nordic Bikes")
- Agency devs as members of each workspace they manage
- The client invited to their workspace, so they can use the AI for daily updates (adding articles, changing hours)
- Quota limits per workspace (1 project, 1 deployment for basic clients; more for premium)
A team might set up:
- One shared workspace for the team, everyone as members
- The team lead as instance admin
## Architecture
```
data/
system.sqlite # Users, workspaces, access grants
workspaces/
{workspaceId}/
osws.sqlite # Projects, files, templates, skills
projects/
{projectId}/
database.sqlite # User-defined project databases
deployments/
{deploymentId}/
runtime.sqlite # Published deployment runtime
analytics.sqlite # Published deployment analytics
public/
deployments/
{deploymentId}/ # Published static files
```
**system.sqlite** is the only shared database. It stores user accounts, workspace definitions, access grants (who can access which workspace), and deployment routing.
**Per-workspace osws.sqlite** contains everything within a workspace. The schema is identical to single-user mode. Isolation is physical (separate files), not logical.
## URL Structure
All workspace pages use the `/w/{workspaceId}/` prefix:
```
/w/{workspaceId}/projects
/w/{workspaceId}/deployments
/w/{workspaceId}/dashboard
/w/{workspaceId}/settings
```
API routes follow the same pattern:
```
/api/w/{workspaceId}/sync/projects
/api/w/{workspaceId}/deployments
/api/w/{workspaceId}/shell/execute
```
System-wide admin pages (no workspace context):
```
/admin/users
/admin/workspaces
/admin/login
```
## Setup
### 1. Initial Setup
On a fresh install with `NEXT_PUBLIC_SERVER_MODE=true`:
1. Visit `/admin` -- you'll be redirected to a registration page
2. Create the admin account (email + password)
3. You're in. The first user automatically becomes admin with an unlimited workspace.
No `ADMIN_PASSWORD` env var is needed for new installs. The legacy admin password only works as a bootstrap mechanism when no user accounts exist.
### 2. Configure Environment
Add to your `.env` alongside standard server mode variables:
| Variable | Default | Description |
|----------|---------|-------------|
| `REGISTRATION_MODE` | `closed` | `open` = users can self-register. `closed` = admin creates accounts |
| `NEXT_PUBLIC_REGISTRATION_MODE` | `closed` | Client-side mirror (controls register link visibility) |
| `INSTANCE_API_KEY` | *(none)* | Admin API accepts `x-instance-api-key` header for programmatic access |
See **[Server Mode](?doc=server-mode)** for the full variable list.
### 3. Choose How Users Join
**Open registration** (`REGISTRATION_MODE=open`): Users visit `/admin/register`, create an account, and get a default workspace automatically.
**Admin-managed** (default): Admin creates users and workspaces via `/admin/users` and `/admin/workspaces`, then grants access.
### 4. Create Workspaces
**Via admin UI** at `/admin/workspaces`:
- Click "New Workspace", set a name and assign an owner
- Expand a workspace row to see members, add or remove access
- Edit quotas (max projects, deployments, storage) per workspace
**Via admin API**:
```
POST /api/admin/workspaces
{ "name": "Sweet Candies", "ownerEmail": "dev@agency.com" }
```
### 5. Grant Access
**Via admin UI**: Expand a workspace, click "Add Member", enter email and role.
**Via admin API**:
```
POST /api/admin/workspaces/{id}/access
{ "email": "client@sweetcandies.com", "role": "editor" }
```
## Admin API Reference
All admin routes require an admin session or the `x-instance-api-key` header.
### Workspace Management
```
GET /api/admin/workspaces -- list all workspaces with stats
POST /api/admin/workspaces -- create workspace
GET /api/admin/workspaces/{id} -- workspace detail + members
PUT /api/admin/workspaces/{id} -- update (name, quotas)
DELETE /api/admin/workspaces/{id} -- delete workspace
POST /api/admin/workspaces/{id}/access -- grant user access
DELETE /api/admin/workspaces/{id}/access -- revoke user access
POST /api/admin/workspaces/{id}/repair -- detect and fix data issues
```
### User Management
```
GET /api/admin/users -- list all users with their workspaces
POST /api/admin/users -- create user account
GET /api/admin/users/{id} -- user detail + workspaces
PUT /api/admin/users/{id} -- update (display name, active)
DELETE /api/admin/users/{id} -- deactivate user
```
### User's Own Workspaces
```
GET /api/workspaces -- list workspaces the current user has access to
```
## Quotas
Each workspace has configurable limits. Defaults:
- 3 projects
- 1 published deployment
- 100 MB storage
Enforced at:
- **Project creation** — rejects sync push when at project limit
- **Deployment publishing** — rejects publish when at deployment limit
- **File sync** — rejects file push when storage limit reached
A warning banner appears in the workspace UI when storage usage exceeds 80%.
Configurable per-workspace via the admin UI or API. An agency might give basic clients 1 project / 1 deployment and premium clients 10 / 5.
## Upgrading from Single-User Mode
Existing single-user instances automatically migrate when multitenancy is enabled:
1. On first login, a default workspace is created with unlimited quotas
2. Existing projects, deployments, templates, and skills are copied from `data/osws.sqlite` to the workspace
3. Project databases from `data/projects/` are copied to the workspace
If migration doesn't complete (e.g., already logged in when workspace was created), a "Workspace Setup Required" dialog offers to re-login and retry. Manual repair is available via:
```
POST /api/admin/workspaces/{id}/repair
```
## Security
- **Physical isolation**: Each workspace has its own SQLite file. No cross-workspace data leakage possible from missing query filters.
- **Role-based access**: Every workspace API request verifies the user has sufficient access via `verifyWorkspaceAccess()`.
- **Path validation**: Workspace IDs validated as UUIDs before file path construction.
- **Timing-safe auth**: API key and password comparisons use constant-time operations.
- **Statement blocking**: ATTACH, DETACH, PRAGMA, VACUUM blocked in user-facing SQL execution.
- **Session validation**: Deactivated users' sessions invalidated on next request.
## Browser Mode Compatibility
All multitenancy code is in server mode code paths, gated by `NEXT_PUBLIC_SERVER_MODE`. Browser mode (IndexedDB, client-side only) is completely unaffected.
|