diff --git a/.github/pr-assets/app-preview-api-key-docs.webp b/.github/pr-assets/app-preview-api-key-docs.webp new file mode 100644 index 000000000..bb3eb50ae Binary files /dev/null and b/.github/pr-assets/app-preview-api-key-docs.webp differ diff --git a/.github/pr-assets/app-preview-api-key-public-api-docs.webp b/.github/pr-assets/app-preview-api-key-public-api-docs.webp new file mode 100644 index 000000000..7f87da525 Binary files /dev/null and b/.github/pr-assets/app-preview-api-key-public-api-docs.webp differ diff --git a/apps/docs/src/content/docs/docs/cli/reference/channel.mdx b/apps/docs/src/content/docs/docs/cli/reference/channel.mdx index 63bc96645..2a471deb7 100644 --- a/apps/docs/src/content/docs/docs/cli/reference/channel.mdx +++ b/apps/docs/src/content/docs/docs/cli/reference/channel.mdx @@ -56,11 +56,17 @@ npx @capgo/cli@latest channel delete production com.example.app | Param | Type | Description | | -------------- | ------------- | -------------------- | | **-a** | string | API key to link to your account | -| **--delete-bundle** | boolean | Delete the bundle associated with the channel | +| **--delete-bundle** | boolean | Delete the bundle associated with the channel. After the App Preview lifecycle release, an App Preview key can atomically delete only its own preview channel and bundle. | | **--success-if-not-found** | boolean | Success if the channel is not found | | **--supa-host** | string | Custom Supabase host URL (for self-hosting or Capgo development) | | **--supa-anon** | string | Custom Supabase anon key (for self-hosting) | +:::caution +**Availability:** The special App Preview cleanup requires the App Preview lifecycle release. Until that release is deployed to your Capgo Cloud environment, `--delete-bundle` requires the general `bundle.delete` permission and is not available to an App Preview key. + +After the release, use an organization- and app-bound **App Preview** API key with `--delete-bundle`. The command uses a narrow atomic cleanup route: it succeeds only for the channel and linked bundle that key created. The key cannot use it on a default/main channel, another preview key's channel, or another key's bundle, and it still does not receive general `bundle.delete`. See [API Keys](/docs/webapp/api-keys/#use-an-app-preview-key-for-preview-workflows) for setup. +::: + ### πŸ“‹ **List** **Alias:** `l` diff --git a/apps/docs/src/content/docs/docs/public-api/api-keys.mdx b/apps/docs/src/content/docs/docs/public-api/api-keys.mdx index 913d03a7b..9818b9179 100644 --- a/apps/docs/src/content/docs/docs/public-api/api-keys.mdx +++ b/apps/docs/src/content/docs/docs/public-api/api-keys.mdx @@ -19,13 +19,43 @@ The `authorization` header is also accepted but is primarily intended for JWT to ## RBAC Permissions -API keys use the same role-based access control (RBAC) system as user accounts. When creating or managing keys through the web app, you assign roles at two levels: +API keys use the same role-based access control (RBAC) system as user accounts. When creating or managing keys through the web app or API, you assign roles at two levels: -- **Organization role** β€” Defines the key's baseline permissions across the entire organization (e.g. `org_admin`, `org_member`). -- **App roles** β€” Optional per-app permissions (e.g. `app_admin`, `app_developer`, `app_uploader`, `app_reader`). +- **Organization role** β€” Defines the key's baseline permissions across the entire organization (for example, `org_admin` or `org_member`). +- **App roles** β€” Per-app permissions (for example, `app_admin`, `app_developer`, `app_uploader`, `app_reader`, or `app_preview`). If an API key has explicit role bindings, **only those bindings** are evaluated for permission checks. The key owner's personal permissions are not inherited by the key. +### Preview channel automation + +After the App Preview lifecycle release is deployed to your Capgo Cloud environment, bind `app_preview` only to the preview app for CI that creates a temporary preview channel, uploads and promotes a bundle, then deletes both. Until then, `--delete-bundle` requires the general `bundle.delete` permission and is not available to an App Preview key. + +```json +{ + "name": "PR preview key", + "hashed": true, + "bindings": [ + { + "role_name": "app_preview", + "scope_type": "app", + "org_id": "", + "app_id": "" + } + ] +} +``` +`org_id` is the UUID of the app's owning organization. `app_id` is the app record's internal UUID, not the public app identifier used by CLI commands (for example, `com.example.app`). The binding remains organization-bound even when the key has no organization-wide role. + +The app-level `app_preview` role includes only `app.read`, `app.read_bundles`, `app.upload_bundle`, and `app.create_channel`. When that key creates a channel, Capgo automatically adds a `channel_preview` binding on the newly created channel. That child binding grants `channel.read`, `channel.promote_bundle`, and `channel.delete` only for the channel the key created. + +`app_preview` retains `app.read`, so this is not strict channel-read isolation: the key may enumerate channel metadata in the selected app. The automatic child binding limits **lifecycle mutations** to the channel the key created. + +Capgo records the App Preview key that uploaded each bundle. The key can promote only its own bundle to its own preview channel. It has no channel lifecycle access to an existing default/main channel, a channel created by another preview key, or another key's bundle. + +Use `channel delete --delete-bundle` for cleanup. This is an atomic, ownership-checked preview cleanup route; it removes only the calling key's preview channel and linked bundle. `app_preview` does not grant generic `bundle.delete`. + +For the dashboard setup and a complete CLI example, see [Use an App Preview key for preview workflows](/docs/webapp/api-keys/#use-an-app-preview-key-for-preview-workflows). + {/* [SCREENSHOT: capgo_apikeys_rbac_diagram.webp] β€” Diagram showing the RBAC hierarchy: org roles β†’ app roles β†’ API key permissions */}
A diagram explaining how RBAC API key permissions work
@@ -94,12 +124,13 @@ Organization policies can enforce: ## Common Use Cases -1. **CI/CD Integration**: Create keys scoped to specific apps with the `app_uploader` or `app_developer` role, and set an expiration date -2. **Deployment Automation**: Use keys with the `app_developer` role for automated deployment scripts -3. **Monitoring Tools**: Create keys with the `app_reader` role for external monitoring integrations -4. **Admin Access**: Use keys with the `org_admin` role sparingly for administrative tools -5. **Third-Party Integrations**: Create keys restricted to specific apps with the minimum required role -6. **Organization Provisioning**: Use an `org_admin` or `org_super_admin` RBAC key with `org.create` only for trusted automation that needs to create organizations +1. **CI/CD Integration**: Create keys scoped to specific apps with the `app_uploader` or `app_developer` role, and set an expiration date. +2. **PR Preview Channels**: Use `app_preview` on only the preview app or apps when CI needs to upload a bundle, create a temporary channel, and atomically clean up its own channel and bundle. +3. **Deployment Automation**: Use keys with the `app_developer` role for automated deployment scripts. +4. **Monitoring Tools**: Create keys with the `app_reader` role for external monitoring integrations. +5. **Admin Access**: Use keys with the `org_admin` role sparingly for administrative tools. +6. **Third-Party Integrations**: Create keys restricted to specific apps with the minimum required role. +7. **Organization Provisioning**: Use an `org_admin` or `org_super_admin` RBAC key with `org.create` only for trusted automation that needs to create organizations. ## Keep going from API Keys diff --git a/apps/docs/src/content/docs/docs/webapp/api-keys.mdx b/apps/docs/src/content/docs/docs/webapp/api-keys.mdx index bacb48e3a..9f3d28130 100644 --- a/apps/docs/src/content/docs/docs/webapp/api-keys.mdx +++ b/apps/docs/src/content/docs/docs/webapp/api-keys.mdx @@ -5,7 +5,7 @@ sidebar: order: 5 --- -API keys are managed at the organization level with role-based access control (RBAC). Each key can be assigned an organization-wide role and optional per-app roles, giving you fine-grained control over what each key can access. +API keys use role-based access control (RBAC). A key can have an organization-wide role, app-scoped bindings, or both. Every app binding remains associated with that app's owning organization. For least privilege, restrict a key to selected apps instead of assigning an organization-wide role. ## Where are API keys managed? @@ -24,40 +24,84 @@ The page displays two sections: 1. Click the **"+"** button at the top of the RBAC keys table. 2. Fill in the **Key information**: - - **Name** (required) β€” A descriptive label for the key (e.g. "CI/CD Deploy", "Monitoring Read-Only"). - - **Create secure key** (optional) β€” When checked, the key will be hashed server-side. The plain-text key is shown **only once** after creation. You will not be able to retrieve it later. - - **Set expiration date** (optional) β€” Pick a date after which the key will stop working. Some organizations enforce mandatory expiration via policy. + - **Name** (required) β€” A descriptive label for the key (for example, "CI/CD Deploy" or "Monitoring Read-Only"). + - **Create secure key** (optional) β€” When checked, the key is hashed server-side. The plain-text key is shown **only once** after creation and cannot be retrieved later. + - **Set expiration date** (optional) β€” Pick a date after which the key stops working. Some organizations enforce mandatory expiration through policy. {/* [SCREENSHOT: apikeys-create-form.webp] β€” The creation form showing Name field, "Create secure key" checkbox, and expiration date picker */}
API key creation form with name, secure key option, and expiration
-3. Select an **Organization role** β€” This defines the key's baseline permissions across the entire organization. Available roles (depending on your own role level): - - **None** β€” No org-wide access; the key only has access to individually assigned apps. - - **Member** β€” Basic read access to the organization. - - **Admin** β€” Full administrative access to the organization (inherits access to all apps). +3. Choose the key scope: + - **Organization-wide access** β€” Select an **Organization role** to define baseline permissions across the organization. Available roles depend on your own role level: + - **None** β€” No organization-wide access. + - **Member** β€” Basic read access to the organization. + - **Admin** β€” Full administrative access to the organization and all its apps. + - **App-only access** β€” Enable **Limit this key to selected apps**. Each app binding remains linked to that app's owning organization; this mode omits only an organization-wide RBAC role. 4. If the key needs to create organizations through the public API, enable **Allow creating organizations**. - This adds the global `org.create` permission. It is available when the key has an Admin organization role. When this key creates a new organization, Capgo automatically assigns the key as Super Admin on that new organization. - -5. If the selected org role is not Admin, you can add **per-app access**: - - Click **"+ Add App"** to open the app picker. - - Select one or more apps, then assign a role to each: - - **App Reader** β€” Read-only access to the app. - - **App Uploader** β€” Can upload new bundles. - - **App Developer** β€” Can manage the app configuration and deployments. - - **App Admin** β€” Full access to the app. + This adds the global `org.create` permission. The key also needs an organization-scoped `org_admin` or `org_super_admin` binding. In the dashboard, selecting **Admin** creates the `org_admin` binding. When this key creates a new organization, Capgo automatically assigns the key as Super Admin on that new organization. +5. To grant app access: + - When **Limit this key to selected apps** is enabled, choose an organization under **Organizations to filter apps** to reveal its apps. The selected app bindings remain associated with their owning organizations; this filter does not grant organization-wide access. + - Click **"+ Add App"**, select one or more apps, then assign a role to each: + - **App Reader** β€” Read-only access to the app. + - **App Uploader** β€” Can upload new bundles. + - **App Developer** β€” Can upload bundles, manage devices, trigger native builds, and update channel settings. + - **App Admin** β€” Full access to the app. + - **App Preview** β€” Preview CI/CD lifecycle: upload a bundle and create a preview channel. The channel receives automatic, channel-scoped lifecycle rights only for this key. {/* [SCREENSHOT: apikeys-rbac-roles.webp] β€” The role selection section showing org role radio buttons and the per-app role assignment table */}
RBAC role selection with org-level and per-app roles
6. Click **"Create"**. -7. If you checked "Create secure key", a modal will display the plain-text key. **Copy it immediately** β€” it cannot be retrieved after closing the modal. +7. If you checked **Create secure key**, a modal displays the plain-text key. **Copy it immediately** β€” it cannot be retrieved after closing the modal. {/* [SCREENSHOT: apikeys-secret-modal.webp] β€” The one-time secret modal showing the plain-text key with a copy button */}
One-time API key secret modal with copy button
+## Use an App Preview key for preview workflows + +Use **App Preview** for CI that creates a temporary preview channel, uploads and promotes a bundle, then removes both the channel and bundle. It is designed for PR previews without granting control over the app's default or main channels. + +:::caution +**Availability:** This ownership-bound lifecycle requires the App Preview lifecycle release. Until that release is deployed to your Capgo Cloud environment, `--delete-bundle` requires the general `bundle.delete` permission and an App Preview key cannot use it. +::: + +1. Create an RBAC key and enable **Limit this key to selected apps**. +2. Under **Organizations to filter apps**, select the organization to reveal its apps; each selected app remains associated with its owning organization. +3. Add only the app or apps used for preview deployments. +4. Select **App Preview** for each selected app. +5. Create a secure key, set an expiration date when appropriate, and store it in your CI secret manager. + +This selected-apps-only mode omits an organization-wide role; it does **not** remove the organization association. Each `app_preview` binding remains scoped to its selected app and that app's owning organization. + +| Grant | Permissions and boundary | +| --- | --- | +| `app_preview` on the selected app | `app.read`, `app.read_bundles`, `app.upload_bundle`, and `app.create_channel`. | +| Automatic `channel_preview` binding | When this key creates a channel, Capgo automatically gives the key `channel.read`, `channel.promote_bundle`, and `channel.delete` for that channel only. | + +`app_preview` retains `app.read`, so the key may enumerate channel metadata in the selected app. The automatic child binding is a **management** boundary: it does not grant lifecycle mutations for channels the key did not create. + +Capgo also records which App Preview key uploaded a bundle. The key can set only its own bundle on its own preview channel and can clean up that pair together. It does not receive a channel binding for an existing default or main channel, a channel created by another preview key, or another key's bundle. + +For a typical CI job, use a unique channel name per pull request: + +```bash +npx @capgo/cli@latest channel add "$PREVIEW_CHANNEL" com.example.app -a "$CAPGO_API_KEY" +npx @capgo/cli@latest bundle upload com.example.app --path ./dist -b "$BUNDLE_VERSION" -a "$CAPGO_API_KEY" +npx @capgo/cli@latest channel set "$PREVIEW_CHANNEL" com.example.app --bundle "$BUNDLE_VERSION" -a "$CAPGO_API_KEY" +npx @capgo/cli@latest channel delete "$PREVIEW_CHANNEL" com.example.app --delete-bundle -a "$CAPGO_API_KEY" +``` + +The last command uses a narrow atomic cleanup route: it succeeds only when the key owns the preview channel and its linked bundle. **App Preview** still does not have general `bundle.delete`, so it cannot delete an arbitrary bundle. + +:::caution +An **App Preview** key cannot promote, delete, or change an existing default/main channel, another preview key's channel, or another key's bundle. Revoking or expiring the app binding also removes the key's effective lifecycle access. +::: + +See the [Access Control Reference](/docs/webapp/organization-roles/#app-preview-permission-set) for the full boundary. + ## How to manage (edit) an API key? Click the **wrench icon** (Manage) on any RBAC key in the list. This opens the key detail page where you can: diff --git a/apps/docs/src/content/docs/docs/webapp/channels.mdx b/apps/docs/src/content/docs/docs/webapp/channels.mdx index 2a4a8c7b0..c725be786 100644 --- a/apps/docs/src/content/docs/docs/webapp/channels.mdx +++ b/apps/docs/src/content/docs/docs/webapp/channels.mdx @@ -88,6 +88,8 @@ If the trash icon is not visible, your account does not have the `channel.delete npx @capgo/cli@latest channel delete ``` +After the App Preview lifecycle release is deployed to your Capgo Cloud environment, an organization- and app-bound **App Preview** key can atomically delete only the preview channel and bundle it created with `channel delete --delete-bundle`. Until that release, `--delete-bundle` requires the general `bundle.delete` permission and is not available to an App Preview key. After release, it cannot change or delete an existing default/main channel, another preview key's channel, or another key's bundle. See [API Keys](/docs/webapp/api-keys/#use-an-app-preview-key-for-preview-workflows) for the least-privilege setup. + ## Managing a channel Clicking on the channel name will open a modal where you can manage the channel settings. (`3` in the image) diff --git a/apps/docs/src/content/docs/docs/webapp/organization-roles.mdx b/apps/docs/src/content/docs/docs/webapp/organization-roles.mdx index 0345479c6..fcd2b8377 100644 --- a/apps/docs/src/content/docs/docs/webapp/organization-roles.mdx +++ b/apps/docs/src/content/docs/docs/webapp/organization-roles.mdx @@ -69,6 +69,7 @@ Scoped to a single app. Use these when a team member should only work on one app | **App Developer** | `app_developer` | Upload bundles, manage devices, trigger native builds, update channel settings. No deletion, no app settings changes, no channel creation. | | **App Uploader** | `app_uploader` | Read access + upload new bundle versions. | | **App Reader** | `app_reader` | Read-only β€” stats, bundles, channels, logs, devices. | +| **App Preview** | `app_preview` | Organization- and app-bound preview CI lifecycle: upload a bundle and create a preview channel. Creating that channel automatically grants lifecycle rights only for it. | ### App permission matrix @@ -92,6 +93,41 @@ Scoped to a single app. Use these when a team member should only work on one app `app.delete` and `app.transfer` are **org-level operations** β€” only `org_super_admin` can perform them, regardless of app-level roles. ::: +### App Preview permission set + +Use **App Preview** (`app_preview`) for an organization- and app-bound CI key that manages a PR preview lifecycle without broad app or organization-wide access. + +:::caution +**Availability:** This ownership-bound lifecycle requires the App Preview lifecycle release. Until that release is deployed to your Capgo Cloud environment, `--delete-bundle` requires the general `bundle.delete` permission and an App Preview key cannot use it. +::: + +The `app_preview` binding grants only these app-level permissions: + +| Permission | Allows | +|------------|--------| +| `app.read` | Read the selected app | +| `app.read_bundles` | Read uploaded bundles | +| `app.upload_bundle` | Upload a bundle | +| `app.create_channel` | Create a channel | + +When an App Preview key creates a channel, Capgo automatically gives that key a child `channel_preview` binding for the new channel only: + +| Permission | Allows | +|------------|--------| +| `channel.read` | Read the channel created by the key | +| `channel.promote_bundle` | Set the key's own uploaded bundle on that channel | +| `channel.delete` | Delete that channel | + +Because `app_preview` retains `app.read`, the key may enumerate channel metadata in the selected app. The automatic child binding is a **management** boundary: it does not grant lifecycle mutations for a channel the key did not create. + +Capgo records the preview key that created each channel and uploaded each bundle. Therefore an App Preview key can create a channel, promote its own bundle, and atomically delete that channel and bundle with `channel delete --delete-bundle`; it cannot do those things to an existing default/main channel, another preview key's channel, or another key's bundle. + +It does not include `app.update_settings`, device or role management, `channel.update_settings`, `channel.rollback_bundle`, forced-device management, or generic `bundle.delete`. + +:::caution +Every App Preview binding remains linked to the selected app's owning organization. The role omits an organization-wide role; it does not remove the organization association. Revoking or expiring the app binding removes the key's effective lifecycle access. +::: + --- ## Channel roles @@ -99,27 +135,28 @@ Scoped to a single app. Use these when a team member should only work on one app Scoped to a single channel. Useful for giving targeted access to a specific release channel. :::note -Channel roles are currently available **via API and CLI only** β€” there is no UI to assign them in the dashboard. In the dashboard, use [channel permission overrides](/docs/webapp/organization-system/#overriding-channel-permissions) instead to fine-tune channel access per user. +Channel roles are currently available **via API and CLI only** β€” there is no UI to assign them in the dashboard. `channel_preview` is an exception: Capgo creates it automatically for an App Preview key when that key creates a channel. In the dashboard, use [channel permission overrides](/docs/webapp/organization-system/#overriding-channel-permissions) instead to fine-tune channel access per user. ::: | Role | Internal name | Description | |------|---------------|-------------| | **Channel Admin** | `channel_admin` | Full control of one channel: settings, promote/rollback bundles, manage forced devices. | | **Channel Viewer** | `channel_reader` | Read-only β€” current bundle, history, forced devices, audit log. | +| **Channel Preview** | `channel_preview` | System-assigned to the App Preview key that created the channel: read, promote its own bundle, and delete that channel. | ### Channel permission matrix -| Permission | Description | Channel Admin | Channel Viewer | -|------------|-------------|:-------------:|:--------------:| -| `channel.read` | View the channel and its current bundle | βœ… | βœ… | -| `channel.update_settings` | Edit channel settings (platform toggles, update policy…) | βœ… | ❌ | -| `channel.delete` | Delete the channel | βœ… | ❌ | -| `channel.read_history` | View bundle assignment history | βœ… | βœ… | -| `channel.promote_bundle` | Set the active bundle on the channel | βœ… | ❌ | -| `channel.rollback_bundle` | Roll back to a previous bundle | βœ… | ❌ | -| `channel.manage_forced_devices` | Force specific devices to this channel | βœ… | ❌ | -| `channel.read_forced_devices` | View the list of forced devices | βœ… | βœ… | -| `channel.read_audit` | View channel activity log | βœ… | βœ… | +| Permission | Description | Channel Admin | Channel Viewer | Channel Preview | +|------------|-------------|:-------------:|:--------------:|:---------------:| +| `channel.read` | View the channel and its current bundle | βœ… | βœ… | βœ… | +| `channel.update_settings` | Edit channel settings (platform toggles, update policy…) | βœ… | ❌ | ❌ | +| `channel.delete` | Delete the channel | βœ… | ❌ | βœ… | +| `channel.read_history` | View bundle assignment history | βœ… | βœ… | ❌ | +| `channel.promote_bundle` | Set the active bundle on the channel | βœ… | ❌ | βœ… | +| `channel.rollback_bundle` | Roll back to a previous bundle | βœ… | ❌ | ❌ | +| `channel.manage_forced_devices` | Force specific devices to this channel | βœ… | ❌ | ❌ | +| `channel.read_forced_devices` | View the list of forced devices | βœ… | βœ… | ❌ | +| `channel.read_audit` | View channel activity log | βœ… | βœ… | ❌ | ---