From 9e6bcde554599e7beb376ddbc12d89e7b0289402 Mon Sep 17 00:00:00 2001 From: Arnob kumar saha Date: Thu, 3 Sep 2026 16:27:13 +0600 Subject: [PATCH] docs(api): sync API reference with the b3 router Add tooling to diff the b3 API server against docs/platform/api, and apply the drift the first run found. Tooling (hack/api-doc-sync/, driven by /api-docs-sync): - extract_routes.py walks the macaron registration call graph from RegisterRoutes and RegisterMarketplaceServiceRoutes, so a helper called inside an m.Group() gets the enclosing path prefix and middlewares. - compare.py diffs the extracted routes against the md pages and openapi.yaml, resolving the page-relative, multi-verb and abbreviated heading forms these pages use. - refresh_reference.py re-inlines openapi.yaml into reference/api.html, which had no generator. Corrections: - user-dashboard.md documented /dashboard/clusters/*, but registerBillingDashboardUserAPIs is called from inside the /user group, so the served prefix is /user/dashboard/clusters. Renamed the 10 headings and the 9 matching openapi.yaml paths. - The same endpoints require view:licensed_clusters, not view:contracts; the two /events routes additionally require view:event_resources. - /healthz and /.well-known/openid-configuration are registered on the accounts router, which is mounted at /accounts - not at the host root as documented. Replaced the OIDC example with the actual template output. Newly documented (md + openapi.yaml): - /orgs/{orgname}/auth-source (GET/POST/PUT/DELETE) - /orgs/{orgname}/subscription (GET/POST/DELETE) - /user/login-method, /user/orgs, /user/inbox/subscriptions - /user/deploy/orders and its render/helm3/yaml routes, replacing a placeholder that deferred to the source - /clusters/{owner}/{cluster}/namespaces/{namespace}/resources - the ui.kubedb.com databaseconfigurations raw passthrough - /dashboard/monthly-summary/{resourceType} Also records the per-verb authorization checks on the cluster, namespace and resource subscription routes. Every route the router registers is now covered by both the md pages and the spec, except the /api/v1 catch-all 404 handler. Signed-off-by: Arnob kumar saha --- .claude/commands/api-docs-sync.md | 133 +++ docs/platform/api/README.md | 2 +- .../api/billing-dashboard/usage-reports.md | 67 ++ .../api/billing-dashboard/user-dashboard.md | 51 +- .../cluster-management-v1/kubernetes-proxy.md | 39 + .../api/cluster-management-v1/lifecycle.md | 51 + .../cluster-management-v2/subscriptions.md | 34 +- .../api/miscellaneous/miscellaneous.md | 61 +- docs/platform/api/miscellaneous/overview.md | 8 +- docs/platform/api/openapi.yaml | 879 +++++++++++++++++- .../api/organizations-teams/organizations.md | 187 ++++ docs/platform/api/reference/api.html | 4 +- .../api/users-settings/authenticated-user.md | 222 ++++- hack/api-doc-sync/compare.py | 164 ++++ hack/api-doc-sync/extract_routes.py | 227 +++++ hack/api-doc-sync/refresh_reference.py | 49 + 16 files changed, 2092 insertions(+), 86 deletions(-) create mode 100644 .claude/commands/api-docs-sync.md create mode 100644 hack/api-doc-sync/compare.py create mode 100644 hack/api-doc-sync/extract_routes.py create mode 100644 hack/api-doc-sync/refresh_reference.py diff --git a/.claude/commands/api-docs-sync.md b/.claude/commands/api-docs-sync.md new file mode 100644 index 0000000..bba1c7a --- /dev/null +++ b/.claude/commands/api-docs-sync.md @@ -0,0 +1,133 @@ +--- +description: Scan the b3 API server against docs/platform/api and update the docs to match +argument-hint: "[path to b3 checkout] [--report-only]" +allowed-tools: Bash, Read, Edit, Write, Grep, Glob +--- + +# Sync `docs/platform/api` with the b3 API server + +`$ARGUMENTS` + +`b3` is the Go API server behind these docs. This command finds every place the +two have drifted and fixes the docs. Default b3 checkout: +`../b3` relative to this repo (i.e. `go.bytebuilders.dev/b3`). If `--report-only` +is passed, produce the ranked findings list and stop — change nothing. + +Docs never drive code: **b3 is always the source of truth.** Never edit b3 to +match the docs, and never invent an endpoint the router does not register. + +## 1. Extract the route table + +```bash +B3=${1:-../b3} +python3 hack/api-doc-sync/extract_routes.py "$B3/routers/api/v1" > /tmp/routes.txt +python3 hack/api-doc-sync/compare.py /tmp/routes.txt docs/platform/api \ + docs/platform/api/openapi.yaml > /tmp/api-diff.txt +``` + +`extract_routes.py` walks the registration call graph from `RegisterRoutes` and +`RegisterMarketplaceServiceRoutes`, so a helper called inside +`m.Group("/user", …)` correctly gets the `/user` prefix. Read its docstring +before trusting an odd-looking path. + +**Baseline:** the last full run extracted **547 routes** (545 unique +method+path) and ended with complete md and openapi.yaml coverage. + +**Sanity gate:** if the route count dropped sharply versus the baseline, or many +paths look truncated, the extractor has stopped understanding a new registration +style (a new `m.Combo` chain shape, a `m.Group` whose path is a constant, a +helper invoked as `pkg.Register(m)` rather than `register(m)`). Fix the +extractor first — a silently short route table produces confidently wrong docs. + +## 2. Triage the diff + +Work the sections in this order; the first three are actionable, the last is +advisory. + +1. **in openapi.yaml but NOT in code** — a documented path that no longer + exists, or a *wrong* path. Before deleting anything, grep the code for the + handler name: a "missing" path is usually a real endpoint under a different + prefix (a helper moved inside another `m.Group`), and the fix is to rename the + path, not drop it. +2. **in CODE but NOT in md** — undocumented endpoints. Write them up (step 3). +3. **in CODE but NOT in openapi.yaml** — add the path/operation to the spec. +4. **in md but NOT in code** — advisory. Overview tables put several verbs on one + line (`| GET/POST/DELETE |`) and curl examples contain concrete values, so + most entries here are noise. Only chase one if it names a plausible endpoint + that `grep` cannot find in b3 at all. + +Known-good false positive — leave it alone: + +- `ANY /*` — the `/api/v1` catch-all 404 handler (`miscellaneous.go`). It is not + an endpoint and is deliberately absent from the docs and the spec. As of the + last run it is the **only** entry in both code→md and code→openapi. + +`compare.py` already resolves the doc conventions this repo uses: absolute +headings, headings relative to a page's declared root ("All routes on this page +are rooted at `/api/v1/user/contracts`"), several verbs sharing one path +(`PUT · PATCH /x`, `| POST/GET/DELETE |`), `/api/v1`-prefixed table entries, and +abbreviated `### GET .../kubeDb/views/x` headings (matched as a path suffix). If a +new page invents another convention, teach the tool rather than rewriting the +page — but a page whose relative headings state no root cannot be matched, so +that page does need a root sentence. + +For each surviving finding, verify against the source before writing a word: +open the `file:line` the extractor printed, read the handler, and derive the +request/response shape from the bound payload type (`binding.Json(X{})` / +`bind(X{})`) and what the handler actually writes (`ctx.JSON(...)`). Cite what +you read. If a shape cannot be determined from the code, say so on the page +instead of inventing fields. + +## 3. Update the docs + +- Put each endpoint on the page for its API group — the group table in + `docs/platform/api/README.md` maps base paths to pages. Match the + surrounding page's existing section style exactly (heading form, auth line, + path/query parameter tables, JSON examples, `Errors:` line, `curl` block). +- Respect each page's declared path root: if the page says paths are relative to + `/api/v1/user/contracts`, write `### GET /{id}`, not the absolute path. +- Middleware → prose mapping, as the existing pages use it: `reqToken()` → + "token required"; `reqSiteAdmin()` / `authzCheck(...:site_admin)` → site admin; + `reqOrgFromQuery()` → resolves the org from `?org=`; `reqClusterAssignment()` + → owner+cluster resolved and a Kubernetes client built; `authzCheck(X, "perm")` + → name the permission string. +- Note availability when the registration is conditional in + `routers/api/v1/api.go`: `setting.AppsCodeHosted` → "AppsCode-hosted only"; + `setting.IsBillingEnabled()` → "billing-enabled deployments only". +- Only add a `> **Verified:** …` note if you actually ran the request against a + live deployment in this session. Never copy one from another endpoint. +- Update `docs/platform/api/openapi.yaml` alongside the md: add/rename the path, + reuse existing `components/schemas` and `parameters` rather than duplicating, + and tag the operation with the same tag its group's siblings use. +- If the API-group set itself changed (a whole new `register*APIs` function), + add a row to the group table and a section page — do not bury a new group + inside an unrelated page. + +## 4. Regenerate and verify + +```bash +python3 hack/api-doc-sync/refresh_reference.py # re-inlines the spec into reference/api.html +python3 -c "import yaml;yaml.safe_load(open('docs/platform/api/openapi.yaml'))" +liche -p -h -l -s # exactly as CI runs it +python3 hack/api-doc-sync/compare.py /tmp/routes.txt docs/platform/api \ + docs/platform/api/openapi.yaml | head -40 +``` + +The `liche` invocation must keep `-s`; this repo's links intentionally carry one +extra `../` (see `CLAUDE.md`). Never "fix" a relative link to match the on-disk +path: a sibling page in the same directory is `../sibling.md`, and a page in +another directory is `../../other-dir/page.md`. + +`docs/platform/api/README.md` fails this check by design — its links use +non-standard paths, which is why CI's liche fork passes `-i '^README\.md$'`. Add +that flag when scanning recursively; the locally installed `liche` may not have +it, in which case just skip `README.md`. + +Re-run `compare.py` at the end and confirm the code→md and code→openapi lists +contain nothing but the known false positives above. + +## 5. Report + +Give a ranked summary: wrong paths first, then missing endpoints, then spec-only +changes. For anything you chose not to change, say why in one line. Note the +route count so the next run can compare. diff --git a/docs/platform/api/README.md b/docs/platform/api/README.md index b529c4c..f39af4b 100644 --- a/docs/platform/api/README.md +++ b/docs/platform/api/README.md @@ -80,7 +80,7 @@ The server supports several authentication mechanisms: | Session cookie | Web console | Cookie-based sign-in; CSRF-protected | | Personal access token / Bearer token | API clients, CLI | `Authorization: token `, `?token=`, `?access_token=` | | Basic auth | Token management endpoints | With optional OTP (2FA) | -| OAuth2 / OIDC | SSO; the KubeDB Platform API Server is both provider and consumer | `/login/oauth/*`, `/.well-known/openid-configuration` | +| OAuth2 / OIDC | SSO; the KubeDB Platform API Server is both provider and consumer | `/accounts/login/oauth/*`, `/accounts/.well-known/openid-configuration` | | LDAP / PAM | Enterprise sign-in sources | Configured by site admins | | 2FA / WebAuthn | User accounts | TOTP, scratch tokens, security keys | | License-based auth | Member clusters | Clusters authenticate with issued licenses / cluster tokens | diff --git a/docs/platform/api/billing-dashboard/usage-reports.md b/docs/platform/api/billing-dashboard/usage-reports.md index e8d42f0..ea0e076 100644 --- a/docs/platform/api/billing-dashboard/usage-reports.md +++ b/docs/platform/api/billing-dashboard/usage-reports.md @@ -45,6 +45,73 @@ Common path parameters for the `summary` routes: | `year` | string | Four-digit report year (e.g. `2026`). | | `month` | string | Report month (e.g. `06` or `June`, per the generated-months list). | +## Monthly summary + +### GET /dashboard/monthly-summary/{resourceType} + +Returns the per-month usage totals for one product, newest month first, with +month-over-month change percentages. + +- **Auth:** token; site-admin (`view_usage_analytics:site_admin`). Requires `?org=`. +- **Path parameters:** + +| Name | Type | Description | +|---|---|---| +| `resourceType` | string | Product alias: `kubedb`, `stash`, `vault`, or `voyager`. | + +- **Query parameters:** + +| Name | Type | Required | Description | +|---|---|---|---| +| `org` | string | yes | Organization slug. | +| `period_start` | string | no | First month to include, `YYYY-MM`. Defaults to five months before the current month (a six-month window). | +| `period_end` | string | no | Last month to include, `YYYY-MM`. Only read when `period_start` is given; defaults to the current month. | + + When an explicit period is given it is clamped to the months that actually have a + generated summary; if it overlaps none, the response comes back with an empty + `months` array. The requested range must not exceed **24 months**. + +- **Response:** `200 OK` — a `MonthlySummaryResponse`: + +```json +{ + "resourceType": "kubedb", + "months": [ + { + "year": 2026, + "month": 6, + "cpuCoreMonth": 12.5, + "memoryGiBMonth": 48.25, + "instances": 7, + "cpuChangeFromPrev": 4.2, + "memoryChangeFromPrev": -1.8 + }, + { + "year": 2026, + "month": 5, + "cpuCoreMonth": 12.0, + "memoryGiBMonth": 49.1, + "instances": 7 + } + ] +} +``` + +| Field | Type | Description | +|---|---|---| +| `resourceType` | string | Echo of the path parameter. | +| `months` | array | Monthly rows, most recent first. | +| `months[].year` | integer | Calendar year. | +| `months[].month` | integer | Calendar month, `1`–`12`. | +| `months[].cpuCoreMonth` | number | CPU core-months consumed. | +| `months[].memoryGiBMonth` | number | Memory GiB-months consumed. | +| `months[].instances` | integer | Instance count for the month. | +| `months[].cpuChangeFromPrev` | number | Percentage change vs. the previous (older) month; omitted on the oldest row. | +| `months[].memoryChangeFromPrev` | number | Percentage change vs. the previous (older) month; omitted on the oldest row. | + +Errors: `400` for an unknown `resourceType`, a `period_start`/`period_end` that is not +`YYYY-MM`, a `period_start` after `period_end`, or a range longer than 24 months. + ## Generated months ### GET /dashboard/summary/generated-months diff --git a/docs/platform/api/billing-dashboard/user-dashboard.md b/docs/platform/api/billing-dashboard/user-dashboard.md index ff67071..699f2f1 100644 --- a/docs/platform/api/billing-dashboard/user-dashboard.md +++ b/docs/platform/api/billing-dashboard/user-dashboard.md @@ -12,21 +12,20 @@ section_menu_id: api # User Billing Dashboard -Owner-scoped endpoints under `/api/v1/dashboard/clusters` that back the self-service +Owner-scoped endpoints under `/api/v1/user/dashboard/clusters` that back the self-service billing dashboard an organization sees for its **own** clusters: active clusters, their licenses and licensed products, and per-cluster / per-license / per-resource event counts. All paths on this page are relative to `/api/v1`. Every endpoint requires `Authorization: token `, resolves the owner from the `org` query -parameter (`?org=`), and requires the `view:contracts` permission on that -organization (this is the org's "hosted mode / view contracts" grant — not +parameter (`?org=`), and requires the `view:licensed_clusters` permission on that +organization (the org's "hosted mode / view licensed clusters" grant — not site-admin). This group is available only on **billing-enabled deployments**. -> **Verified:** every endpoint on this page returned `404 Not Found` against -> `appscode` on `` on 2026-07-14 — this deployment is not billing-enabled, -> so the `/dashboard/clusters/*` routes are not registered. (Sanity: `GET /version` -> and `GET /user` returned `200` with the same token.) +> **Note.** These paths were previously documented without the `/user` prefix. +> `registerBillingDashboardUserAPIs` registers `/dashboard/clusters` from inside the +> `/user` group, so the served prefix is `/api/v1/user/dashboard/clusters`. Shared conventions on this page: @@ -37,12 +36,12 @@ Shared conventions on this page: ## Clusters -### GET /dashboard/clusters/active +### GET /user/dashboard/clusters/active Lists clusters that reported within the `limit` window for the owner resolved from the query. -- **Auth:** token; owner-scoped (`view:contracts`). Requires `?org=`. +- **Auth:** token; owner-scoped (`view:licensed_clusters`). Requires `?org=`. - **Query parameters:** | Name | Type | Required | Description | @@ -66,16 +65,16 @@ the query. ``` curl -H "Authorization: token $AKP_TOKEN" \ - "https:///api/v1/dashboard/clusters/active?org=appscode" + "https:///api/v1/user/dashboard/clusters/active?org=appscode" ``` > **Verified:** returned `404` against `appscode` — billing not enabled on this deployment. -### GET /dashboard/clusters/{cid} +### GET /user/dashboard/clusters/{cid} Returns cluster information for a cluster owned by the request owner. -- **Auth:** token; owner-scoped (`view:contracts`). Requires `?org=`. +- **Auth:** token; owner-scoped (`view:licensed_clusters`). Requires `?org=`. - **Path parameters:** | Name | Type | Description | @@ -89,11 +88,11 @@ Returns cluster information for a cluster owned by the request owner. > **Verified:** returned `404` against `appscode` — billing not enabled. -### GET /dashboard/clusters/{cid}/events-count +### GET /user/dashboard/clusters/{cid}/events-count Returns today's event count for the cluster. -- **Auth:** token; owner-scoped (`view:contracts`). Requires `?org=`. +- **Auth:** token; owner-scoped (`view:licensed_clusters`). Requires `?org=`. - **Path parameters:** `cid` (string) — cluster UID. - **Response:** `200 OK` — an `EventsCounterResponse`. @@ -113,11 +112,11 @@ the license/resource-scoped counter endpoints below; `error` is set instead of ## Licenses -### GET /dashboard/clusters/{cid}/licenses/ +### GET /user/dashboard/clusters/{cid}/licenses/ Lists licensed plans associated with the request owner and cluster. -- **Auth:** token; owner-scoped (`view:contracts`). Requires `?org=`. +- **Auth:** token; owner-scoped (`view:licensed_clusters`). Requires `?org=`. - **Path parameters:** `cid` (string) — cluster UID. - **Response:** `200 OK` — an array of `LicensedPlan` (see the Admin Billing Dashboard page for the full shape). @@ -138,11 +137,11 @@ Lists licensed plans associated with the request owner and cluster. > **Verified:** returned `404` against `appscode` — billing not enabled. -### GET /dashboard/clusters/{cid}/licenses/{lid} +### GET /user/dashboard/clusters/{cid}/licenses/{lid} Returns a licensed plan (API form) associated with the request owner. -- **Auth:** token; owner-scoped (`view:contracts`). Requires `?org=`. +- **Auth:** token; owner-scoped (`view:licensed_clusters`). Requires `?org=`. - **Path parameters:** `cid` (string), `lid` (string) — license ID. - **Response:** `200 OK` — a `LicensePlanApiForm`. @@ -166,22 +165,22 @@ Returns a licensed plan (API form) associated with the request owner. ## Event counts and events -### GET /dashboard/clusters/{cid}/licenses/{lid}/products/{product}/events-count +### GET /user/dashboard/clusters/{cid}/licenses/{lid}/products/{product}/events-count Returns today's event count for a license/product. -- **Auth:** token; owner-scoped (`view:contracts`). Requires `?org=`. +- **Auth:** token; owner-scoped (`view:licensed_clusters`). Requires `?org=`. - **Path parameters:** `cid` (string), `lid` (string), `product` (string). - **Response:** `200 OK` — an `EventsCounterResponse` (with `product` and `licenseID` populated). > **Verified:** returned `404` against `appscode` — billing not enabled. -### GET /dashboard/clusters/{cid}/licenses/{lid}/products/{product}/groups/{group}/resources/{resource}/{rid}/events-count +### GET /user/dashboard/clusters/{cid}/licenses/{lid}/products/{product}/groups/{group}/resources/{resource}/{rid}/events-count Returns today's event count for a specific resource object. -- **Auth:** token; owner-scoped (`view:contracts`). Requires `?org=`. +- **Auth:** token; owner-scoped (`view:licensed_clusters`). Requires `?org=`. - **Path parameters:** | Name | Type | Description | @@ -211,11 +210,11 @@ Returns today's event count for a specific resource object. > **Verified:** returned `404` against `appscode` — billing not enabled. -### GET /dashboard/clusters/{cid}/licenses/{lid}/products/{product}/events/ +### GET /user/dashboard/clusters/{cid}/licenses/{lid}/products/{product}/events/ Returns the tabular event list for the caller's cluster/license/product. -- **Auth:** token; owner-scoped (`view:contracts`). Requires `?org=`. +- **Auth:** token; owner-scoped (`view:licensed_clusters` plus `view:event_resources`). Requires `?org=`. - **Path parameters:** `cid` (string), `lid` (string), `product` (string). - **Query parameters:** @@ -240,11 +239,11 @@ Returns the tabular event list for the caller's cluster/license/product. > **Verified:** returned `404` against `appscode` — billing not enabled. -### GET /dashboard/clusters/{cid}/licenses/{lid}/products/{product}/events/raw-event +### GET /user/dashboard/clusters/{cid}/licenses/{lid}/products/{product}/events/raw-event Returns the raw badger value for a specific event key/version. -- **Auth:** token; owner-scoped (`view:contracts`). Requires `?org=`. +- **Auth:** token; owner-scoped (`view:licensed_clusters` plus `view:event_resources`). Requires `?org=`. - **Path parameters:** `cid` (string), `lid` (string), `product` (string). - **Query parameters:** diff --git a/docs/platform/api/cluster-management-v1/kubernetes-proxy.md b/docs/platform/api/cluster-management-v1/kubernetes-proxy.md index bd36335..8d79f24 100644 --- a/docs/platform/api/cluster-management-v1/kubernetes-proxy.md +++ b/docs/platform/api/cluster-management-v1/kubernetes-proxy.md @@ -479,6 +479,45 @@ Produce a `policy.k8s.appscode.com` `PolicyReport`. --- +## Database configurations (ui.kubedb.com) + +### GET /clusters/{owner}/{cluster}/proxy/ui.kubedb.com/v1alpha1/namespaces/{namespace}/databaseconfigurations/{name} + +Get a `ui.kubedb.com/v1alpha1` `DatabaseConfiguration` for one database object. + +This route exists separately from the generic namespaced get because it is served by a +**raw REST passthrough** rather than the dynamic client: `DatabaseConfiguration` is +backed by an extended apiserver with its own get options (for example `?keys=`), and +the dynamic client can only carry `metav1.GetOptions` and would drop them. Every query +parameter is therefore forwarded to the member cluster verbatim, **except** `filter` +and `convertToTable`, which the KubeDB Platform API Server consumes itself. + +- **Auth:** token. + +**Path parameters:** + +| Name | Type | Description | +|---|---|---| +| `namespace` | string | Namespace of the database object. | +| `name` | string | Name of the database object. | + +**Query parameters:** + +| Name | Type | Required | Description | +|---|---|---|---| +| `filter` | string | no | Content filter applied to the response by the platform (not forwarded). | +| `convertToTable` | string | no | Return table output instead of the object (not forwarded). | +| *(anything else)* | string | no | Forwarded verbatim to the extended apiserver, e.g. `keys`. | + +**Response:** `200` with the `DatabaseConfiguration` object, an `ETag` header, and a +`Cache-Control` header; `304 Not Modified` when the request's `If-None-Match` matches. +`resourceVersion` is cleared from the response so the ETag stays stable. + +Errors: the member cluster's status code is passed through, except that a `401` from +the cluster is reported as `403`. + +--- + ## Batch delete ### POST /clusters/{owner}/{cluster}/proxy/batch-delete diff --git a/docs/platform/api/cluster-management-v1/lifecycle.md b/docs/platform/api/cluster-management-v1/lifecycle.md index 44a0d2b..e8c25a4 100644 --- a/docs/platform/api/cluster-management-v1/lifecycle.md +++ b/docs/platform/api/cluster-management-v1/lifecycle.md @@ -169,6 +169,57 @@ The empty-string key (`""`) is the legacy Kubernetes core group. > **Verified:** `GET` returned `200` against `appscode/ace` (hub) and `appscode/arnob-dev` (spoke) on 2026-07-14. +### GET /clusters/{owner}/{cluster}/namespaces/{namespace}/resources + +List the objects that exist in one namespace, for a chosen set of API groups. Uses +server-preferred namespaced resources, and only resources that support `list`; group +discovery failures for individual groups are skipped rather than failing the request. +Objects the caller cannot list (`403`), resources that are not found (`404`), and +resources that do not support listing (`405`) are silently omitted. The whole request +is bounded by a 30-second timeout, with at most 5 resource types listed concurrently. + +- **Auth:** token. + +**Path parameters:** + +| Name | Type | Description | +|---|---|---| +| `namespace` | string | Namespace to list. | + +**Query parameters:** + +| Name | Type | Required | Description | +|---|---|---|---| +| `groups` | string | yes | Comma-separated API groups to include. Use `core` for the legacy Kubernetes core group (sent to the cluster as the empty group). Duplicates and blanks are ignored. | + +**Response:** `200` — a flat array of objects: + +```json +[ + { + "group": "kubedb.com", + "version": "v1", + "resource": "mongodbs", + "kind": "MongoDB", + "name": "mgo", + "namespace": "demo" + }, + { + "group": "", + "version": "v1", + "resource": "configmaps", + "kind": "ConfigMap", + "name": "kube-root-ca.crt", + "namespace": "demo" + } +] +``` + +Errors: `400` when `namespace` is empty or `groups` resolves to no groups. + +Use [`GET .../available-types`](#get-clustersownerclusteravailable-types) first to see +which groups the cluster actually serves. + ### GET /clusters/{owner}/{cluster}/is-server Check whether this cluster is the KubeDB Platform hub cluster. diff --git a/docs/platform/api/cluster-management-v2/subscriptions.md b/docs/platform/api/cluster-management-v2/subscriptions.md index b412cc2..2be685b 100644 --- a/docs/platform/api/cluster-management-v2/subscriptions.md +++ b/docs/platform/api/cluster-management-v2/subscriptions.md @@ -28,6 +28,16 @@ Common conventions: - Each subscription resource supports three verbs on the same path: `POST` to subscribe, `GET` to check the subscription, and `DELETE` to unsubscribe. All return `200` on success (with no body). +- Beyond the token, every subscription route runs an authorization check on the cluster: + `POST` and `DELETE` need the **editor** relation, `GET` only the **viewer** relation. + The permission string names the scope — `subscribe:cluster`, `subscribe:namespace`, or + `subscribe:resource`. +- All of them additionally require the cluster to be connected and the platform's inbox + service to be reachable; when the inbox backend is missing the routes return `500`. +- Organization-level subscriptions live on the + [Organizations](../../organizations-teams/organizations.md) page, and + [`GET /user/inbox/subscriptions`](../../users-settings/authenticated-user.md) lists + everything the caller is subscribed to across all scopes. Example request: @@ -55,13 +65,13 @@ Path: `/clustersv2/{owner}/{cluster}/subscriptions/` Subscribe the current user to cluster-level inbox notifications. -**Auth:** token. **Response:** `200` — subscription created (no body). +**Auth:** token + `authzCheck(subscribe:cluster)` (`Cluster_Editor`). **Response:** `200` — subscription created (no body). ### GET /clustersv2/{owner}/{cluster}/subscriptions/ Check the current user's cluster-level inbox subscription. -**Auth:** token. **Response:** `200` — subscription exists (no body). +**Auth:** token + `authzCheck(subscribe:cluster)` (`Cluster_Viewer`). **Response:** `200` — subscription exists (no body). > **Verified:** `GET` returned `500` against `appscode/ace` on 2026-07-14 — checking a > subscription requires the inbox/notification backend, which is not provisioned on this @@ -71,7 +81,7 @@ Check the current user's cluster-level inbox subscription. Remove the cluster-level inbox subscription. -**Auth:** token. **Response:** `200` — subscription removed (no body). +**Auth:** token + `authzCheck(subscribe:cluster)` (`Cluster_Editor`). **Response:** `200` — subscription removed (no body). --- @@ -93,19 +103,19 @@ Path: `/clustersv2/{owner}/{cluster}/subscriptions/namespaces/{namespace}/` Subscribe the current user to namespace-level inbox notifications. -**Auth:** token. **Response:** `200` — subscription created (no body). +**Auth:** token + `authzCheck(subscribe:namespace)` (`Cluster_Editor`). **Response:** `200` — subscription created (no body). ### GET /clustersv2/{owner}/{cluster}/subscriptions/namespaces/{namespace}/ Check the current user's namespace-level inbox subscription. -**Auth:** token. **Response:** `200` — subscription exists (no body). +**Auth:** token + `authzCheck(subscribe:namespace)` (`Cluster_Viewer`). **Response:** `200` — subscription exists (no body). ### DELETE /clustersv2/{owner}/{cluster}/subscriptions/namespaces/{namespace}/ Remove the namespace-level inbox subscription. -**Auth:** token. **Response:** `200` — subscription removed (no body). +**Auth:** token + `authzCheck(subscribe:namespace)` (`Cluster_Editor`). **Response:** `200` — subscription removed (no body). --- @@ -135,23 +145,23 @@ Example path for a KubeDB MongoDB named `mg-shard` in namespace `demo`: /clustersv2/appscode/arnob-dev/subscriptions/namespaces/demo/kubedb.com/v1/mongodbs/mg-shard ``` -### POST .../{group}/{version}/{resource}/{resourceName} +### POST /clustersv2/{owner}/{cluster}/subscriptions/namespaces/{namespace}/{group}/{version}/{resource}/{resourceName} Subscribe the current user to resource-level inbox notifications. -**Auth:** token. **Response:** `200` — subscription created (no body). +**Auth:** token + `authzCheck(subscribe:resource)` (`Cluster_Editor`). **Response:** `200` — subscription created (no body). -### GET .../{group}/{version}/{resource}/{resourceName} +### GET /clustersv2/{owner}/{cluster}/subscriptions/namespaces/{namespace}/{group}/{version}/{resource}/{resourceName} Check the current user's resource-level inbox subscription. -**Auth:** token. **Response:** `200` — subscription exists (no body). +**Auth:** token + `authzCheck(subscribe:resource)` (`Cluster_Viewer`). **Response:** `200` — subscription exists (no body). -### DELETE .../{group}/{version}/{resource}/{resourceName} +### DELETE /clustersv2/{owner}/{cluster}/subscriptions/namespaces/{namespace}/{group}/{version}/{resource}/{resourceName} Remove the resource-level inbox subscription. -**Auth:** token. **Response:** `200` — subscription removed (no body). +**Auth:** token + `authzCheck(subscribe:resource)` (`Cluster_Editor`). **Response:** `200` — subscription removed (no body). --- diff --git a/docs/platform/api/miscellaneous/miscellaneous.md b/docs/platform/api/miscellaneous/miscellaneous.md index bef3dd3..924055f 100644 --- a/docs/platform/api/miscellaneous/miscellaneous.md +++ b/docs/platform/api/miscellaneous/miscellaneous.md @@ -14,8 +14,8 @@ section_menu_id: api Utility endpoints of the KubeDB Platform API Server. Unless noted otherwise, paths on this page are relative to `/api/v1` — the full base path is `https:///api/v1`. Two -endpoints (`/healthz` and `/.well-known/openid-configuration`) are served at the -**host root** and are shown with their full path. +endpoints (`/accounts/healthz` and `/accounts/.well-known/openid-configuration`) are +served by the **accounts router** instead and are shown with their full path. All endpoints on this page are **public** — no authentication is required. @@ -130,12 +130,16 @@ curl https:///api/v1/swagger > **Verified:** `GET` returned `200` (Swagger UI HTML page) against the platform on 2026-07-14; Swagger is enabled on this deployment. -## Health & OIDC discovery (host root) +## Health & OIDC discovery (accounts router) -The following two endpoints are **not** under the `/api/v1` prefix — they are served -at the host root. Use their full paths. +The following two endpoints are **not** under the `/api/v1` prefix. They are +registered on the accounts (web console) router, which the server mounts under +`/accounts` (`AccountsSubURL`), so the served paths are `/accounts/healthz` and +`/accounts/.well-known/openid-configuration`. A deployment may additionally expose +them at the host root through its ingress; the paths below are the ones the server +itself registers. -### GET /healthz +### GET /accounts/healthz Liveness/health check for the server. @@ -146,12 +150,17 @@ Liveness/health check for the server. Example: ``` -curl https:///healthz +curl https:///accounts/healthz ``` -> **Verified:** `GET https:///healthz` returned `200` on 2026-07-14. On this deployment the host root serves the KubeDB Platform web console single-page app, so the response body was the console HTML (catch-all) rather than a plain health payload; the `200` still confirms the server is reachable and healthy. +The handler writes the literal body `OK`. -### GET /.well-known/openid-configuration +> **Note.** An earlier version of this page documented this endpoint at +> `/healthz` and recorded a `200` for it. That response was the web console's +> single-page-app catch-all, not this handler — the host root serves the console on a +> typical deployment. The registered path is the one above. + +### GET /accounts/.well-known/openid-configuration Standard OpenID Connect discovery document. The KubeDB Platform API Server is itself an OIDC provider (for SSO), and this endpoint advertises its issuer and the authorization/token/userinfo/JWKS @@ -162,23 +171,39 @@ endpoints so OIDC clients can auto-configure. **Response:** `200 OK` with the OIDC discovery JSON (issuer, endpoint URLs, supported scopes, response types, and signing algorithms), for example: +All endpoint URLs are built from the deployment's accounts base URL, so on a default +install they sit under `/accounts/`: + ```json { - "issuer": "https://", - "authorization_endpoint": "https:///login/oauth/authorize", - "token_endpoint": "https:///login/oauth/access_token", - "userinfo_endpoint": "https:///login/oauth/userinfo", - "jwks_uri": "https:///login/oauth/keys", - "response_types_supported": ["code"], + "issuer": "https:///accounts/", + "authorization_endpoint": "https:///accounts/login/oauth/authorize", + "token_endpoint": "https:///accounts/login/oauth/access_token", + "jwks_uri": "https:///accounts/login/oauth/keys", + "userinfo_endpoint": "https:///accounts/login/oauth/userinfo", + "introspection_endpoint": "https:///accounts/login/oauth/introspect", + "response_types_supported": ["code", "id_token"], + "id_token_signing_alg_values_supported": ["RS256"], "subject_types_supported": ["public"], - "id_token_signing_alg_values_supported": ["RS256"] + "scopes_supported": ["openid", "profile", "email", "groups"], + "claims_supported": [ + "aud", "exp", "iat", "iss", "sub", "name", "preferred_username", "profile", + "picture", "website", "locale", "updated_at", "email", "email_verified", "groups" + ], + "code_challenge_methods_supported": ["plain", "S256"], + "grant_types_supported": ["authorization_code", "refresh_token"] } ``` +`id_token_signing_alg_values_supported` reports the algorithm of the server's actual +signing key, so it can differ from `RS256`. + Example: ``` -curl https:///.well-known/openid-configuration +curl https:///accounts/.well-known/openid-configuration ``` -> **Verified:** `GET https:///.well-known/openid-configuration` returned `200` on 2026-07-14. On this deployment the host root serves the KubeDB Platform web console single-page app, so the request was answered by the console catch-all (HTML) rather than the OIDC discovery JSON documented above. The example above reflects the endpoint's documented shape; the exact endpoint URLs vary by deployment. +> **Note.** An earlier version of this page documented this endpoint at +> `/.well-known/openid-configuration` and recorded a `200` for it; that response came +> from the web console's single-page-app catch-all, not from this handler. diff --git a/docs/platform/api/miscellaneous/overview.md b/docs/platform/api/miscellaneous/overview.md index c9a4e9e..267a3ab 100644 --- a/docs/platform/api/miscellaneous/overview.md +++ b/docs/platform/api/miscellaneous/overview.md @@ -26,8 +26,8 @@ root**, not under `/api/v1`; their full paths are documented as-is. | GET | `/api/v1/version` | Public | Server version | | GET | `/api/v1/swagger` | Public (if enabled) | Swagger UI | | POST | `/api/v1/markdown`, `/api/v1/markdown/raw` | Public | Render markdown to HTML | -| GET | `/healthz` | Public | Health check (non-API root) | -| GET | `/.well-known/openid-configuration` | Public | OIDC discovery (non-API root) | +| GET | `/accounts/healthz` | Public | Health check (accounts router, not `/api/v1`) | +| GET | `/accounts/.well-known/openid-configuration` | Public | OIDC discovery (accounts router, not `/api/v1`) | Web (non-API) routes also exist for the sign-in/sign-up UI, OAuth2 authorize/token/userinfo endpoints, account activation & recovery, 2FA/WebAuthn login, and static assets. @@ -36,5 +36,5 @@ account activation & recovery, 2FA/WebAuthn login, and static assets. - [Miscellaneous Endpoints](../miscellaneous) — server version (`/api/v1/version`), markdown rendering (`/api/v1/markdown`, `/api/v1/markdown/raw`), - the Swagger UI (`/api/v1/swagger`), the health check (`/healthz`), and OIDC discovery - (`/.well-known/openid-configuration`). + the Swagger UI (`/api/v1/swagger`), the health check (`/accounts/healthz`), and OIDC + discovery (`/accounts/.well-known/openid-configuration`). diff --git a/docs/platform/api/openapi.yaml b/docs/platform/api/openapi.yaml index a1319b8..0a4072f 100644 --- a/docs/platform/api/openapi.yaml +++ b/docs/platform/api/openapi.yaml @@ -17080,13 +17080,13 @@ paths: $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' - /dashboard/clusters/active: + /user/dashboard/clusters/active: get: tags: - Billing Dashboard summary: List the caller's active clusters description: Lists clusters that reported within the `limit` window for the owner resolved from - the query. Requires `view:contracts` authorization and a billing-enabled deployment. + the query. Requires `view:licensed_clusters` authorization and a billing-enabled deployment. operationId: dashboard_ListActiveClusters security: - AuthorizationHeaderToken: [] @@ -17112,12 +17112,12 @@ paths: $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' - /dashboard/clusters/{cid}: + /user/dashboard/clusters/{cid}: get: tags: - Billing Dashboard summary: Get cluster information (caller) - description: Returns cluster information for a cluster owned by the request owner. Requires `view:contracts` + description: Returns cluster information for a cluster owned by the request owner. Requires `view:licensed_clusters` authorization and a billing-enabled deployment. operationId: dashboard_GetClusterInformation security: @@ -17144,12 +17144,12 @@ paths: $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' - /dashboard/clusters/{cid}/events-count: + /user/dashboard/clusters/{cid}/events-count: get: tags: - Billing Dashboard summary: Get cluster events count (caller) - description: Returns today's event count for the cluster. Requires `view:contracts` authorization + description: Returns today's event count for the cluster. Requires `view:licensed_clusters` authorization and a billing-enabled deployment. operationId: dashboard_GetClusterEventsCount security: @@ -17174,12 +17174,12 @@ paths: $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' - /dashboard/clusters/{cid}/licenses: + /user/dashboard/clusters/{cid}/licenses: get: tags: - Billing Dashboard summary: List licenses on a cluster (caller) - description: Lists licensed plans associated with the request owner and cluster. Requires `view:contracts` + description: Lists licensed plans associated with the request owner and cluster. Requires `view:licensed_clusters` authorization and a billing-enabled deployment. operationId: dashboard_ListLicenses security: @@ -17206,12 +17206,12 @@ paths: $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' - /dashboard/clusters/{cid}/licenses/{lid}: + /user/dashboard/clusters/{cid}/licenses/{lid}: get: tags: - Billing Dashboard summary: Get a license (caller) - description: Returns a licensed plan (API form) associated with the request owner. Requires `view:contracts` + description: Returns a licensed plan (API form) associated with the request owner. Requires `view:licensed_clusters` authorization and a billing-enabled deployment. operationId: dashboard_GetLicense security: @@ -17242,12 +17242,12 @@ paths: $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' - /dashboard/clusters/{cid}/licenses/{lid}/products/{product}/events-count: + /user/dashboard/clusters/{cid}/licenses/{lid}/products/{product}/events-count: get: tags: - Billing Dashboard summary: Get license events count (caller) - description: Returns today's event count for a license/product. Requires `view:contracts` authorization + description: Returns today's event count for a license/product. Requires `view:licensed_clusters` authorization and a billing-enabled deployment. operationId: dashboard_GetLicenseEventsCount security: @@ -17284,12 +17284,12 @@ paths: $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' - /dashboard/clusters/{cid}/licenses/{lid}/products/{product}/groups/{group}/resources/{resource}/{rid}/events-count: + /user/dashboard/clusters/{cid}/licenses/{lid}/products/{product}/groups/{group}/resources/{resource}/{rid}/events-count: get: tags: - Billing Dashboard summary: Get resource events count (caller) - description: Returns today's event count for a specific resource object. Requires `view:contracts` + description: Returns today's event count for a specific resource object. Requires `view:licensed_clusters` authorization and a billing-enabled deployment. operationId: dashboard_GetResourceEventsCount security: @@ -17344,12 +17344,12 @@ paths: $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' - /dashboard/clusters/{cid}/licenses/{lid}/products/{product}/events: + /user/dashboard/clusters/{cid}/licenses/{lid}/products/{product}/events: get: tags: - Billing Dashboard summary: List events for a license (caller) - description: Returns the tabular event list for the caller's cluster/license/product. Requires `view:contracts` + description: Returns the tabular event list for the caller's cluster/license/product. Requires `view:licensed_clusters` authorization and a billing-enabled deployment. operationId: dashboard_GetEventsListForLicenseUser security: @@ -17394,12 +17394,12 @@ paths: $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' - /dashboard/clusters/{cid}/licenses/{lid}/products/{product}/events/raw-event: + /user/dashboard/clusters/{cid}/licenses/{lid}/products/{product}/events/raw-event: get: tags: - Billing Dashboard summary: Get raw event data (caller) - description: Returns the raw badger value for a specific event key/version. Requires `view:contracts` + description: Returns the raw badger value for a specific event key/version. Requires `view:licensed_clusters` authorization and a billing-enabled deployment. operationId: dashboard_GetRawEventDataUser security: @@ -19214,6 +19214,645 @@ paths: text/html: schema: type: string + /orgs/{orgname}/auth-source: + get: + tags: + - Identity - Organizations & Teams + summary: Get the organization's SSO auth source + description: Returns the authentication source configured for the organization. The client secret + is never returned. Requires a token and viewer authorization (Organization_Viewer / view:auth-source). + operationId: orgs_GetOrgAuthSource + security: + - AuthorizationHeaderToken: [] + - Token: [] + - AccessToken: [] + parameters: + - $ref: '#/components/parameters/orgnameParam' + responses: + '200': + description: The organization's auth source. + content: + application/json: + schema: + $ref: '#/components/schemas/OrgAuthSourceResponse' + '401': + $ref: '#/components/responses/unauthorized' + '403': + $ref: '#/components/responses/forbidden' + '404': + $ref: '#/components/responses/notFound' + post: + tags: + - Identity - Organizations & Teams + summary: Create the organization's SSO auth source + description: Creates the organization's authentication source and marks it active. Fails with 409 + when one already exists. Requires a token and edit-org authorization (Organization_CanEditOrg + / create:auth-source). + operationId: orgs_CreateOrgAuthSource + security: + - AuthorizationHeaderToken: [] + - Token: [] + - AccessToken: [] + parameters: + - $ref: '#/components/parameters/orgnameParam' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/OrgAuthSourceOption' + responses: + '201': + description: Auth source created. + content: + application/json: + schema: + $ref: '#/components/schemas/OrgAuthSourceResponse' + '401': + $ref: '#/components/responses/unauthorized' + '403': + $ref: '#/components/responses/forbidden' + '409': + description: An auth source already exists for this organization. + content: + application/json: + schema: + $ref: '#/components/schemas/APIError' + put: + tags: + - Identity - Organizations & Teams + summary: Update the organization's SSO auth source + description: Replaces the organization's authentication source settings. An empty or omitted clientSecret + keeps the stored secret. Requires a token and edit-org authorization (Organization_CanEditOrg + / update:auth-source). + operationId: orgs_UpdateOrgAuthSource + security: + - AuthorizationHeaderToken: [] + - Token: [] + - AccessToken: [] + parameters: + - $ref: '#/components/parameters/orgnameParam' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/OrgAuthSourceUpdateOption' + responses: + '200': + description: Auth source updated. + content: + application/json: + schema: + $ref: '#/components/schemas/OrgAuthSourceResponse' + '401': + $ref: '#/components/responses/unauthorized' + '403': + $ref: '#/components/responses/forbidden' + '404': + $ref: '#/components/responses/notFound' + delete: + tags: + - Identity - Organizations & Teams + summary: Delete the organization's SSO auth source + description: Removes the organization's authentication source; members fall back to password login. + Requires a token and edit-org authorization (Organization_CanEditOrg / delete:auth-source). + operationId: orgs_DeleteOrgAuthSource + security: + - AuthorizationHeaderToken: [] + - Token: [] + - AccessToken: [] + parameters: + - $ref: '#/components/parameters/orgnameParam' + responses: + '204': + description: Auth source removed. + '401': + $ref: '#/components/responses/unauthorized' + '403': + $ref: '#/components/responses/forbidden' + /orgs/{orgname}/subscription: + get: + tags: + - Identity - Organizations & Teams + summary: Check the caller's organization inbox subscription + description: Reports whether the calling user is subscribed to the organization's inbox notification + group. Only individual users may subscribe. Requires a token and organization membership, and + the platform inbox service must be reachable. + operationId: orgs_CheckOrgSubscription + security: + - AuthorizationHeaderToken: [] + - Token: [] + - AccessToken: [] + parameters: + - $ref: '#/components/parameters/orgnameParam' + responses: + '200': + description: The subscription exists (empty body). + '400': + description: The path does not resolve to an organization. + content: + application/json: + schema: + $ref: '#/components/schemas/APIError' + '401': + $ref: '#/components/responses/unauthorized' + '403': + $ref: '#/components/responses/forbidden' + post: + tags: + - Identity - Organizations & Teams + summary: Subscribe the caller to the organization inbox + description: Subscribes the calling user to the organization's inbox notification group. Only individual + users may subscribe. Takes no request body. + operationId: orgs_CreateOrgSubscription + security: + - AuthorizationHeaderToken: [] + - Token: [] + - AccessToken: [] + parameters: + - $ref: '#/components/parameters/orgnameParam' + responses: + '200': + description: Subscribed (empty body). + '400': + description: The path does not resolve to an organization. + content: + application/json: + schema: + $ref: '#/components/schemas/APIError' + '401': + $ref: '#/components/responses/unauthorized' + '403': + $ref: '#/components/responses/forbidden' + delete: + tags: + - Identity - Organizations & Teams + summary: Unsubscribe the caller from the organization inbox + description: Removes the calling user from the organization's inbox notification group and deletes + the subscription record. + operationId: orgs_RemoveOrgSubscription + security: + - AuthorizationHeaderToken: [] + - Token: [] + - AccessToken: [] + parameters: + - $ref: '#/components/parameters/orgnameParam' + responses: + '200': + description: Unsubscribed (empty body). + '400': + description: The path does not resolve to an organization. + content: + application/json: + schema: + $ref: '#/components/schemas/APIError' + '401': + $ref: '#/components/responses/unauthorized' + '403': + $ref: '#/components/responses/forbidden' + /user/login-method: + get: + tags: + - Identity - Users & Settings + summary: Resolve how a user or organization should sign in + description: 'Reports whether sign-in should use a password or be redirected to the organization''s + SSO provider. Public: no authentication. Supply at least one of orgname or username; an inactive + org auth source is ignored, and the username fallback only considers organizations whose auth + source enforces SSO for members.' + operationId: user_GetLoginMethod + security: [] + parameters: + - name: orgname + in: query + required: false + description: Organization slug whose auth source should be looked up. + schema: + type: string + - name: username + in: query + required: false + description: Email or username, used as a fallback when orgname is not supplied. + schema: + type: string + responses: + '200': + description: The resolved login method. + content: + application/json: + schema: + $ref: '#/components/schemas/LoginMethodResponse' + '400': + description: Neither orgname nor username was supplied. + content: + application/json: + schema: + $ref: '#/components/schemas/APIError' + /user/orgs: + get: + tags: + - Identity - Organizations & Teams + summary: List the caller's organizations + description: Lists the organizations the authenticated user belongs to, including private memberships. + operationId: orgs_ListMyOrgs + security: + - AuthorizationHeaderToken: [] + - Token: [] + - AccessToken: [] + responses: + '200': + description: Organizations the caller belongs to. + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Organization' + '401': + $ref: '#/components/responses/unauthorized' + /user/inbox/subscriptions: + get: + tags: + - Identity - Users & Settings + summary: List the caller's inbox subscriptions + description: Lists the authenticated user's inbox subscriptions across every scope (organization, + cluster, namespace, resource). All query parameters are optional exact-match filters. The subscriber + is always the caller. + operationId: user_ListInboxSubscriptions + security: + - AuthorizationHeaderToken: [] + - Token: [] + - AccessToken: [] + parameters: + - name: scope + in: query + required: false + description: Subscription scope - org, cluster, namespace, or resource. + schema: + type: string + - name: orgID + in: query + required: false + schema: + type: string + - name: orgName + in: query + required: false + schema: + type: string + - name: clusterName + in: query + required: false + schema: + type: string + - name: clusterUID + in: query + required: false + schema: + type: string + - name: namespaceName + in: query + required: false + schema: + type: string + - name: namespaceUID + in: query + required: false + schema: + type: string + - name: apiGroup + in: query + required: false + schema: + type: string + - name: version + in: query + required: false + schema: + type: string + - name: resource + in: query + required: false + schema: + type: string + - name: resourceName + in: query + required: false + schema: + type: string + - name: resourceUID + in: query + required: false + schema: + type: string + responses: + '200': + description: The caller's inbox subscriptions. + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/InboxSubscription' + '401': + $ref: '#/components/responses/unauthorized' + /user/deploy/orders: + post: + tags: + - Identity - Users & Settings + summary: Create a deployment order + description: Stores a releases.x-helm.dev/v1alpha1 Order in the platform package store so it can + be previewed and rendered. spec.packages must be non-empty. metadata.uid and metadata.creationTimestamp + are assigned by the server; metadata.name defaults to the release name of the first package. + operationId: user_CreateDeployOrder + security: + - AuthorizationHeaderToken: [] + - Token: [] + - AccessToken: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/K8sObject' + responses: + '200': + description: The stored Order, including the server-assigned metadata.uid. + content: + application/json: + schema: + $ref: '#/components/schemas/K8sObject' + '400': + description: spec.packages is empty. + content: + application/json: + schema: + $ref: '#/components/schemas/APIError' + '401': + $ref: '#/components/responses/unauthorized' + /user/deploy/orders/{id}/render/manifest: + get: + tags: + - Identity - Users & Settings + summary: Render a deployment order to a manifest + description: Renders the stored order into a single concatenated manifest, returned as a raw response + body rather than JSON. Requires the platform Helm chart registry to be reachable. + operationId: user_PreviewOrderManifest + security: + - AuthorizationHeaderToken: [] + - Token: [] + - AccessToken: [] + parameters: + - $ref: '#/components/parameters/orderIDParam' + responses: + '200': + description: The rendered manifest. + content: + text/plain: + schema: + type: string + '401': + $ref: '#/components/responses/unauthorized' + /user/deploy/orders/{id}/render/resources: + get: + tags: + - Identity - Users & Settings + summary: Render a deployment order to resource lists + description: Renders the stored order into per-chart resource lists. Requires the platform Helm + chart registry to be reachable. + operationId: user_PreviewOrderResources + security: + - AuthorizationHeaderToken: [] + - Token: [] + - AccessToken: [] + parameters: + - $ref: '#/components/parameters/orderIDParam' + - name: skipCRDs + in: query + required: false + description: Drop each chart's CRDs from the output. + schema: + type: boolean + - name: format + in: query + required: false + description: Output data format for the rendered templates. Defaults to YAML. + schema: + type: string + responses: + '200': + description: The converted chart templates. + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/K8sObject' + '401': + $ref: '#/components/responses/unauthorized' + /user/deploy/orders/{id}/helm3: + get: + tags: + - Identity - Users & Settings + summary: Generate a Helm 3 script for a deployment order + description: Generates a Helm 3 installation script for the stored order. Requires the platform + Helm chart registry to be reachable. + operationId: user_GetOrderHelm3Script + security: + - AuthorizationHeaderToken: [] + - Token: [] + - AccessToken: [] + parameters: + - $ref: '#/components/parameters/orderIDParam' + responses: + '200': + description: The generated script. + content: + application/json: + schema: + type: string + '401': + $ref: '#/components/responses/unauthorized' + /user/deploy/orders/{id}/yaml: + get: + tags: + - Identity - Users & Settings + summary: Generate a YAML script for a deployment order + description: Generates a kubectl-oriented YAML installation script for the stored order. Requires + the platform Helm chart registry to be reachable. + operationId: user_GetOrderYAMLScript + security: + - AuthorizationHeaderToken: [] + - Token: [] + - AccessToken: [] + parameters: + - $ref: '#/components/parameters/orderIDParam' + responses: + '200': + description: The generated script. + content: + application/json: + schema: + type: string + '401': + $ref: '#/components/responses/unauthorized' + /clusters/{owner}/{cluster}/namespaces/{namespace}/resources: + get: + tags: + - Cluster Management v1 + summary: List the objects in a namespace for selected API groups + description: Lists objects in one namespace across the requested API groups, using server-preferred + namespaced resources and only those that support list. Objects the caller cannot list, resources + that are not found, and resources that do not support listing are omitted rather than failing + the request. Bounded by a 30 second timeout with at most 5 resource types listed concurrently. + operationId: clusters_ListNamespaceResources + security: + - AuthorizationHeaderToken: [] + - Token: [] + - AccessToken: [] + parameters: + - $ref: '#/components/parameters/ownerParam' + - $ref: '#/components/parameters/clusterParam' + - name: namespace + in: path + required: true + description: Namespace to list. + schema: + type: string + - name: groups + in: query + required: true + description: Comma-separated API groups to include. Use `core` for the legacy Kubernetes core + group. + schema: + type: string + responses: + '200': + description: The objects found in the namespace. + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/NamespaceResource' + '400': + description: The namespace is empty or groups resolved to no groups. + content: + application/json: + schema: + $ref: '#/components/schemas/APIError' + '401': + $ref: '#/components/responses/unauthorized' + '403': + $ref: '#/components/responses/forbidden' + /clusters/{owner}/{cluster}/proxy/ui.kubedb.com/v1alpha1/namespaces/{namespace}/databaseconfigurations/{name}: + get: + tags: + - Kubernetes Proxy + summary: Get a DatabaseConfiguration + description: Gets a ui.kubedb.com/v1alpha1 DatabaseConfiguration through a raw REST passthrough + rather than the dynamic client, so the extended apiserver's own get options (for example `keys`) + survive. Every query parameter is forwarded verbatim except `filter` and `convertToTable`, which + the platform consumes itself. The response carries an ETag and Cache-Control header and `resourceVersion` + is cleared so the ETag stays stable. A 401 from the member cluster is reported as 403. + operationId: proxy_GetDatabaseConfiguration + security: + - AuthorizationHeaderToken: [] + - Token: [] + - AccessToken: [] + parameters: + - $ref: '#/components/parameters/ownerParam' + - $ref: '#/components/parameters/clusterParam' + - name: namespace + in: path + required: true + description: Namespace of the database object. + schema: + type: string + - name: name + in: path + required: true + description: Name of the database object. + schema: + type: string + - name: filter + in: query + required: false + description: Content filter applied by the platform; not forwarded to the cluster. + schema: + type: string + - name: convertToTable + in: query + required: false + description: Return table output instead of the object; not forwarded to the cluster. + schema: + type: string + responses: + '200': + description: The DatabaseConfiguration object. + content: + application/json: + schema: + $ref: '#/components/schemas/K8sObject' + '304': + description: Not modified - the request's If-None-Match matched the current ETag. + '401': + $ref: '#/components/responses/unauthorized' + '403': + $ref: '#/components/responses/forbidden' + '404': + $ref: '#/components/responses/notFound' + /dashboard/monthly-summary/{resourceType}: + get: + tags: + - Billing Dashboard + summary: Get per-month usage totals for a product + description: Returns per-month usage totals for one product, newest month first, with month-over-month + change percentages. Defaults to a six-month window ending at the current month. An explicit period + is clamped to the months that have a generated summary and must not span more than 24 months. + Site-admin only (view_usage_analytics:site_admin). Requires a billing-enabled deployment. + operationId: dashboard_GetMonthlySummary + security: + - AuthorizationHeaderToken: [] + - Token: [] + - AccessToken: [] + parameters: + - name: resourceType + in: path + required: true + description: Product alias - kubedb, stash, vault, or voyager. + schema: + type: string + - $ref: '#/components/parameters/orgnameQuery' + - name: period_start + in: query + required: false + description: First month to include, as YYYY-MM. + schema: + type: string + - name: period_end + in: query + required: false + description: Last month to include, as YYYY-MM. Only read when period_start is given. + schema: + type: string + responses: + '200': + description: Monthly usage totals for the product. + content: + application/json: + schema: + $ref: '#/components/schemas/MonthlySummaryResponse' + '400': + description: Unknown resourceType, malformed period, or a range longer than 24 months. + content: + application/json: + schema: + $ref: '#/components/schemas/APIError' + '401': + $ref: '#/components/responses/unauthorized' + '403': + $ref: '#/components/responses/forbidden' components: securitySchemes: AuthorizationHeaderToken: @@ -19253,6 +19892,13 @@ components: name: Sudo description: Site-admin impersonation of the named user. parameters: + orderIDParam: + name: id + in: path + required: true + description: Deployment order UID returned by POST /user/deploy/orders. + schema: + type: string ownerParam: name: owner in: path @@ -23064,3 +23710,200 @@ components: properties: version: type: string + + OrgAuthSourceOption: + type: object + description: Request body for creating an organization's SSO auth source. + required: + - name + - provider + - clientID + - clientSecret + properties: + name: + type: string + description: Display name of the auth source, e.g. "Acme SSO". + provider: + type: string + description: goth/OAuth2 provider type, e.g. "openidConnect". + clientID: + type: string + description: OAuth2 client ID. + clientSecret: + type: string + description: OAuth2 client secret. Write-only; never returned in a response. + discoveryURL: + type: string + description: OIDC discovery document URL. Required in practice for OIDC providers. + enforceForMembers: + type: boolean + description: When true, blocks password login for every member of the organization. + OrgAuthSourceUpdateOption: + type: object + description: Request body for updating an organization's SSO auth source. An omitted or empty clientSecret + keeps the stored secret. + required: + - name + - provider + - clientID + properties: + name: + type: string + provider: + type: string + description: goth/OAuth2 provider type, e.g. "openidConnect". + clientID: + type: string + clientSecret: + type: string + description: New OAuth2 client secret. Omit or send empty to keep the current secret. + discoveryURL: + type: string + enforceForMembers: + type: boolean + OrgAuthSourceResponse: + type: object + description: An organization's SSO auth source. The client secret is intentionally omitted. + properties: + id: + type: integer + format: int64 + description: Auth source ID (the organization's own ID, not a global login_source). + orgID: + type: integer + format: int64 + description: Owning organization ID. + name: + type: string + provider: + type: string + clientID: + type: string + discoveryURL: + type: string + isActive: + type: boolean + description: Whether the source is active. Set to true on create. + enforceForMembers: + type: boolean + LoginMethodResponse: + type: object + description: How a user or organization should sign in. + properties: + method: + type: string + description: Either "password" or "sso". + orgAuthSourceID: + type: integer + format: int64 + description: The organization's own auth source ID. Only set when method is "sso". + providerName: + type: string + description: goth/OAuth2 provider name, e.g. "openidConnect". Only set when method is "sso". + oauthCallbackPath: + type: string + description: Path the frontend should redirect to, of the form /user/oauth2/org-. Only + set when method is "sso". + displayName: + type: string + description: Human-readable auth source name. Only set when method is "sso". + InboxSubscription: + type: object + description: One inbox notification subscription of a user. + properties: + id: + type: integer + format: int64 + subscriberID: + type: integer + format: int64 + description: Subscriber (user) ID. + subscriberType: + type: string + description: Subscriber account type. + scope: + type: string + description: One of org, cluster, namespace, resource. + orgID: + type: string + orgName: + type: string + clusterOwnerID: + type: integer + format: int64 + clusterUID: + type: string + clusterName: + type: string + namespaceUID: + type: string + namespaceName: + type: string + apiGroup: + type: string + version: + type: string + resource: + type: string + resourceName: + type: string + resourceUID: + type: string + createdAt: + type: string + format: date-time + updatedAt: + type: string + format: date-time + NamespaceResource: + type: object + description: One object found in a namespace, identified by its GVR and name. + properties: + group: + type: string + description: API group. Empty string for the Kubernetes core group. + version: + type: string + resource: + type: string + description: Plural resource name. + kind: + type: string + name: + type: string + namespace: + type: string + MonthlyUsageSummary: + type: object + description: Billing totals for one product in one month, with month-over-month change percentages. + properties: + year: + type: integer + month: + type: integer + description: Calendar month, 1-12. + cpuCoreMonth: + type: number + description: CPU core-months consumed. + memoryGiBMonth: + type: number + description: Memory GiB-months consumed. + instances: + type: integer + cpuChangeFromPrev: + type: number + description: Percentage change vs. the previous (older) month. Omitted on the oldest row. + memoryChangeFromPrev: + type: number + description: Percentage change vs. the previous (older) month. Omitted on the oldest row. + MonthlySummaryResponse: + type: object + description: Per-month usage totals for one product, ordered from the most recent month to the oldest. + properties: + resourceType: + type: string + description: Product alias the summary covers. + months: + type: array + items: + $ref: '#/components/schemas/MonthlyUsageSummary' diff --git a/docs/platform/api/organizations-teams/organizations.md b/docs/platform/api/organizations-teams/organizations.md index 432960f..b33e94d 100644 --- a/docs/platform/api/organizations-teams/organizations.md +++ b/docs/platform/api/organizations-teams/organizations.md @@ -706,3 +706,190 @@ Revoke a NATS user-type token of the organization's system admin. **Response:** `200 OK` on success. `400` if the organization system admin is missing or the NATS user is not found. + +## Organization SSO auth source + +An organization may own **one** authentication source, used to sign its members in +through an external OAuth2/OIDC provider instead of a password. When +`enforceForMembers` is set, password login is blocked for every member of the org and +`GET /user/login-method` reports `sso` for them (see +[Authenticated User](../../users-settings/authenticated-user.md)). + +The client secret is write-only: it is never returned by any of these endpoints. + +`OrgAuthSourceResponse` — the shape returned by `GET`, `POST`, and `PUT`: + +| Field | Type | Description | +|---|---|---| +| `id` | integer (int64) | Auth source ID (the org's own ID, not a global `login_source`). | +| `orgID` | integer (int64) | Owning organization ID. | +| `name` | string | Human-readable name, e.g. `Acme SSO`. | +| `provider` | string | goth/OAuth2 provider type, e.g. `openidConnect`. | +| `clientID` | string | OAuth2 client ID. | +| `discoveryURL` | string | OIDC discovery document URL; omitted when empty. | +| `isActive` | boolean | Whether the source is active. Set to `true` on create. | +| `enforceForMembers` | boolean | When `true`, blocks password login for all org members. | + +### GET /orgs/{orgname}/auth-source + +Get the organization's authentication source. + +- **Auth:** token + `authzCheck(view:auth-source)` (`Organization_Viewer`). + +**Path parameters:** + +| Name | Type | Description | +|---|---|---| +| `orgname` | string | Organization slug. | + +**Response:** `200 OK` (`OrgAuthSourceResponse`): + +```json +{ + "id": 7, + "orgID": 3, + "name": "Acme SSO", + "provider": "openidConnect", + "clientID": "", + "discoveryURL": "https://sso.example.com/.well-known/openid-configuration", + "isActive": true, + "enforceForMembers": true +} +``` + +`404` when the organization has no authentication source. + +### POST /orgs/{orgname}/auth-source + +Create the organization's authentication source. Fails with `409 Conflict` if one +already exists — use `PUT` to change it. + +- **Auth:** token + `authzCheck(create:auth-source)` (`Organization_CanEditOrg`). + +**Path parameters:** + +| Name | Type | Description | +|---|---|---| +| `orgname` | string | Organization slug. | + +**Request body** (`OrgAuthSourceOption`): + +```json +{ + "name": "Acme SSO", + "provider": "openidConnect", + "clientID": "", + "clientSecret": "", + "discoveryURL": "https://sso.example.com/.well-known/openid-configuration", + "enforceForMembers": true +} +``` + +| Field | Type | Required | Description | +|---|---|---|---| +| `name` | string | yes | Display name of the source. | +| `provider` | string | yes | goth/OAuth2 provider type, e.g. `openidConnect`. | +| `clientID` | string | yes | OAuth2 client ID. | +| `clientSecret` | string | yes | OAuth2 client secret. Never returned in a response. | +| `discoveryURL` | string | no | Required in practice for OIDC providers. | +| `enforceForMembers` | boolean | no | Block password login for all org members. | + +**Response:** `201 Created` (`OrgAuthSourceResponse`, with `isActive: true`). + +`409` if a source already exists for the organization. + +### PUT /orgs/{orgname}/auth-source + +Replace the organization's authentication source settings. + +- **Auth:** token + `authzCheck(update:auth-source)` (`Organization_CanEditOrg`). + +**Path parameters:** + +| Name | Type | Description | +|---|---|---| +| `orgname` | string | Organization slug. | + +**Request body** (`OrgAuthSourceUpdateOption`) — same fields as the create body, except +that `clientSecret` is optional: **omit it or send an empty string to keep the stored +secret**. + +**Response:** `200 OK` (`OrgAuthSourceResponse`). + +`404` when the organization has no authentication source to update. + +### DELETE /orgs/{orgname}/auth-source + +Remove the organization's authentication source. Members fall back to password login. + +- **Auth:** token + `authzCheck(delete:auth-source)` (`Organization_CanEditOrg`). + +**Path parameters:** + +| Name | Type | Description | +|---|---|---| +| `orgname` | string | Organization slug. | + +**Response:** `204 No Content` on success. + +## Organization inbox subscription + +Subscribe the calling user to the organization's inbox notification group. Only +**individual** users can subscribe; a request made as an organization account returns +`403`. All three endpoints require the platform's inbox service to be reachable. + +Cluster-, namespace-, and resource-scoped subscriptions live on the +[Cluster Subscriptions](../../cluster-management-v2/subscriptions.md) page; the list of +everything the caller is subscribed to is +[`GET /user/inbox/subscriptions`](../../users-settings/authenticated-user.md). + +### GET /orgs/{orgname}/subscription + +Check whether the calling user is subscribed to the organization. + +- **Auth:** token + org membership. + +**Path parameters:** + +| Name | Type | Description | +|---|---|---| +| `orgname` | string | Organization slug. | + +**Response:** `200 OK` with an empty body when the subscription exists. + +`403` if the caller is not an individual user; `400` if `orgname` does not resolve to an +organization. + +### POST /orgs/{orgname}/subscription + +Subscribe the calling user to the organization. + +- **Auth:** token + org membership. + +**Path parameters:** + +| Name | Type | Description | +|---|---|---| +| `orgname` | string | Organization slug. | + +**Response:** `200 OK` with an empty body. Takes no request body. + +`403` if the caller is not an individual user; `400` if `orgname` does not resolve to an +organization. + +### DELETE /orgs/{orgname}/subscription + +Unsubscribe the calling user from the organization. + +- **Auth:** token + org membership. + +**Path parameters:** + +| Name | Type | Description | +|---|---|---| +| `orgname` | string | Organization slug. | + +**Response:** `200 OK` with an empty body. + +`403` if the caller is not an individual user; `400` if `orgname` does not resolve to an +organization. diff --git a/docs/platform/api/reference/api.html b/docs/platform/api/reference/api.html index cddeb72..738feec 100644 --- a/docs/platform/api/reference/api.html +++ b/docs/platform/api/reference/api.html @@ -14,13 +14,13 @@
KubeDB Platform — API Reference - OpenAPI 3.0.3 · generated from the KubeDB Platform API · 443 paths / 530 operations + OpenAPI 3.0.3 · generated from the KubeDB Platform API · 456 paths / 544 operations