@checkstack/automation-frontend 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +166 -0
- package/package.json +16 -15
- package/src/pages/AutomationEditPage.tsx +31 -1
- package/tsconfig.json +3 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,171 @@
|
|
|
1
1
|
# @checkstack/automation-frontend
|
|
2
2
|
|
|
3
|
+
## 0.7.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- d2077bd: Platform-wide team-scoped access control on a unified relation-tuple store.
|
|
8
|
+
|
|
9
|
+
Admins can scope any resource to teams, and the **platform** (not each plugin)
|
|
10
|
+
enforces it. A plugin opts in declaratively by adding `instanceAccess` to a
|
|
11
|
+
procedure's contract; the auth middleware does the rest, so enforcement is
|
|
12
|
+
consistent across catalog, health checks, incidents, maintenances, SLOs,
|
|
13
|
+
automations, and the dependency map, and any third-party plugin gets it for free.
|
|
14
|
+
|
|
15
|
+
Core model:
|
|
16
|
+
|
|
17
|
+
- **Teams are optional.** A resource with no team grants behaves exactly as
|
|
18
|
+
before.
|
|
19
|
+
- **Team grants are additive and restrict who can CHANGE a resource, not who can
|
|
20
|
+
SEE it.** Granting a team `Manage` lets its members view and change the
|
|
21
|
+
resource; `Read-only` lets them view it. Either level grants access to team
|
|
22
|
+
members **even when they lack the global permission**, and granting never
|
|
23
|
+
removes read from anyone who already had it (e.g. a public status page stays
|
|
24
|
+
readable). Privacy is a separate, explicit opt-in via the **Private** toggle,
|
|
25
|
+
which removes the global read path so only the resource's teams can see it.
|
|
26
|
+
- **Ownership at creation.** Create forms expose an **Owning team** picker. A
|
|
27
|
+
non-admin can create a resource for a team they belong to that holds a
|
|
28
|
+
create-capability grant for that type; the new resource is auto-granted to that
|
|
29
|
+
team. Incidents and maintenances are **parent-gated**: anyone who can manage a
|
|
30
|
+
system may open incidents/maintenances for it, no separate grant needed.
|
|
31
|
+
- **Meaningful authorization errors.** A caller with neither the global rule nor
|
|
32
|
+
any team grant for a resource type gets a `403` with a structured body instead
|
|
33
|
+
of a silently-empty `200`. Anonymous callers on public endpoints are never
|
|
34
|
+
`403`'d, so status pages keep rendering.
|
|
35
|
+
|
|
36
|
+
Unified relation-tuple store:
|
|
37
|
+
|
|
38
|
+
- The previously separate access primitives (`resource_team_access.canRead` /
|
|
39
|
+
`.canManage`, ownership, `resource_access_settings.teamOnly`, and
|
|
40
|
+
`resource_create_grant`) are collapsed onto ONE
|
|
41
|
+
`relation_tuple(object, relation, subject)` store: "a team has
|
|
42
|
+
`viewer`/`editor`/`owner` on an object, or `creator` on a type". Privacy is an
|
|
43
|
+
explicit **`private` marker** tuple — its **presence** closes the global read
|
|
44
|
+
path (team grants only), its **absence** is the readable-by-default state, so a
|
|
45
|
+
private resource with zero grants is correctly inaccessible to everyone rather
|
|
46
|
+
than silently globalized. The access decision is a pure, unit-tested function.
|
|
47
|
+
- The auth API is generic: `writeRelation` / `removeRelation` / `setObjectPublic`
|
|
48
|
+
/ `listObjectRelations` / `listSubjectRelations` / `setCreateGrant` /
|
|
49
|
+
`listTeamCreateGrants` (user-facing) and `check` / `listAccessibleObjectIds` /
|
|
50
|
+
`hasAnyTypeGrant` / `authorizeCreate` / `setOwner` / `deleteObjectRelations`
|
|
51
|
+
(service-to-service). Migration `0008` backfills tuples from the legacy tables
|
|
52
|
+
and drops them.
|
|
53
|
+
|
|
54
|
+
Explicit per-procedure scoping:
|
|
55
|
+
|
|
56
|
+
- Access rules (`access()` / `accessPair()`) define only the rule (id, level,
|
|
57
|
+
defaults); every procedure declares its own `instanceAccess`. This removes a
|
|
58
|
+
"loaded gun" default that silently applied a shared `idParam` to any procedure
|
|
59
|
+
which forgot its own override.
|
|
60
|
+
- Modes: `idParam` (single-resource pre-check, fails **closed** if the id does
|
|
61
|
+
not resolve), `listKey` / `recordKey` (post-filter a list/record to the
|
|
62
|
+
accessible subset), `create` (authorize creation + write the owning-team
|
|
63
|
+
grant), `parentScope` (scope by read/manage access to a PARENT type,
|
|
64
|
+
cross-plugin single-hop: "you may see incidents/maintenances/SLOs/health for
|
|
65
|
+
system S iff you may see S"), and `global: true` (the honest "intentionally not
|
|
66
|
+
team-scoped" opt-out). A boot-time validator **rejects** any procedure gated on
|
|
67
|
+
a team-scopable resource type that declares no `instanceAccess`, turning the
|
|
68
|
+
previous fail-open into a boot error.
|
|
69
|
+
|
|
70
|
+
Teams administration:
|
|
71
|
+
|
|
72
|
+
- **Team managers** manage their own team's members and managers without the
|
|
73
|
+
global `auth.teams.manage` rule; creating, deleting, and granting a team access
|
|
74
|
+
remain admin-only.
|
|
75
|
+
- A **standalone Teams page** (gated on `auth.teams.read`) lets managers reach
|
|
76
|
+
team administration without the admin Auth Settings page; members are added via
|
|
77
|
+
a debounced directory picker.
|
|
78
|
+
- A **cross-plugin `ResourceResolverRegistry`** lets owning plugins register a
|
|
79
|
+
name/search resolver for their resource types, so the Teams page lists a team's
|
|
80
|
+
grants **by name** (grouped by type) and offers a resource picker — an admin can
|
|
81
|
+
change a grant's level, revoke it, or add one, without auth depending on every
|
|
82
|
+
plugin. Resolvers shipped for catalog systems, health-check configurations,
|
|
83
|
+
incidents, maintenances, SLO objectives, and automations.
|
|
84
|
+
|
|
85
|
+
Frontend:
|
|
86
|
+
|
|
87
|
+
- The resource-side editor is **"Who can change this"** (one Manage checkbox per
|
|
88
|
+
team; unticked = read-only), with an always-visible **Private** toggle
|
|
89
|
+
(disabled until a team that can Manage exists, so a resource can't be stranded).
|
|
90
|
+
- `TeamOwnershipPicker` explains _why_ there's nothing to pick (not a member of
|
|
91
|
+
any team, or none of your teams manage the selected parent) instead of a bare
|
|
92
|
+
"global resource" line.
|
|
93
|
+
- Read-only **"who can change this"** indicators on resource detail pages expand
|
|
94
|
+
to the actual people by name; bulk + per-row **Scope to team** actions in the
|
|
95
|
+
catalog systems list; and the team-access copy spells out that grants are
|
|
96
|
+
additive and that Read-only grants view (not change) even without the global
|
|
97
|
+
permission.
|
|
98
|
+
|
|
99
|
+
Security hardening:
|
|
100
|
+
|
|
101
|
+
- Child deletes in catalog (`removeSystemContact` / `removeSystemLink`) are scoped
|
|
102
|
+
to both the child id and its parent `systemId`, closing a cross-system IDOR for
|
|
103
|
+
team-scoped managers.
|
|
104
|
+
- `searchUsers` is restricted to team administrators, closing a directory/email
|
|
105
|
+
enumeration path opened by the default `auth.teams.read` rule.
|
|
106
|
+
- Grant setters reject unregistered resource types.
|
|
107
|
+
|
|
108
|
+
BREAKING CHANGES (beta; shipped as minor bumps):
|
|
109
|
+
|
|
110
|
+
- `access()` and `accessPair()` no longer accept `idParam` / `listKey` /
|
|
111
|
+
`recordKey`; move instance config to the procedure's `instanceAccess`.
|
|
112
|
+
- Boot fails if a procedure gated on a team-scopable resource type omits
|
|
113
|
+
`instanceAccess`. Declare a scoping mode or `instanceAccess: { global: true }`.
|
|
114
|
+
- The `AuthService` interface is reshaped: `check`, `listAccessibleObjectIds`,
|
|
115
|
+
`hasAnyTypeGrant`, `authorizeCreate` (returns `isPrivate`), `setOwner`
|
|
116
|
+
(`isPrivate`), and `deleteObjectRelations`. Custom `AuthService` implementations
|
|
117
|
+
and mocks must update.
|
|
118
|
+
- The auth RPC contract's per-concept resource-access endpoints are replaced by
|
|
119
|
+
the generic tuple API above; external callers of the old
|
|
120
|
+
`getResourceTeamAccess` / `setResourceTeamAccess` / `setResourceAccessSettings`
|
|
121
|
+
/ `grantResourceCreate` / etc. must move to the new procedures.
|
|
122
|
+
- Several contract inputs changed from a bare `string` to an object so the
|
|
123
|
+
middleware can resolve the resource id: catalog `deleteSystem` (`{ id }`),
|
|
124
|
+
`removeSystemContact` / `removeSystemLink` (`{ id, systemId }`); health-check
|
|
125
|
+
`deleteConfiguration` / `pauseConfiguration` / `resumeConfiguration` (`{ id }`).
|
|
126
|
+
All in-tree callers are updated.
|
|
127
|
+
- List/record endpoints that relied on returning an empty `200` to signal "no
|
|
128
|
+
access" now return a `403` for categorically-unauthorized principals.
|
|
129
|
+
- The mis-keyed bulk endpoints `getBulkIncidentsForSystems`,
|
|
130
|
+
`getBulkMaintenancesForSystems`, and `getBulkObjectivesForSystems` no longer
|
|
131
|
+
post-filter their (systemId-keyed) result; access is already gated by
|
|
132
|
+
`catalog.system` upstream.
|
|
133
|
+
- Team membership/manager mutations (`addUserToTeam`, `removeUserFromTeam`,
|
|
134
|
+
`addTeamManager`, `removeTeamManager`) now require `auth.teams.read` instead of
|
|
135
|
+
`auth.teams.manage` at the contract level (broadened to per-team managers).
|
|
136
|
+
- The `resource_team_access`, `resource_access_settings`, and
|
|
137
|
+
`resource_create_grant` tables are dropped (data backfilled into
|
|
138
|
+
`relation_tuple` by migration `0008`). A previously inconsistent "team-only with
|
|
139
|
+
zero grants" resource is now correctly inaccessible to global-access holders.
|
|
140
|
+
|
|
141
|
+
### Patch Changes
|
|
142
|
+
|
|
143
|
+
- Updated dependencies [551eaa9]
|
|
144
|
+
- Updated dependencies [d2077bd]
|
|
145
|
+
- Updated dependencies [551eaa9]
|
|
146
|
+
- Updated dependencies [9ab73c5]
|
|
147
|
+
- Updated dependencies [5c6393f]
|
|
148
|
+
- @checkstack/ai-common@0.5.0
|
|
149
|
+
- @checkstack/auth-common@0.10.0
|
|
150
|
+
- @checkstack/auth-frontend@0.8.0
|
|
151
|
+
- @checkstack/common@0.16.0
|
|
152
|
+
- @checkstack/automation-common@0.7.0
|
|
153
|
+
- @checkstack/catalog-common@2.4.0
|
|
154
|
+
- @checkstack/ui@1.16.1
|
|
155
|
+
- @checkstack/frontend-api@0.10.0
|
|
156
|
+
- @checkstack/gitops-frontend@0.5.8
|
|
157
|
+
- @checkstack/integration-common@0.9.1
|
|
158
|
+
- @checkstack/script-packages-frontend@0.3.12
|
|
159
|
+
- @checkstack/secrets-frontend@0.2.7
|
|
160
|
+
- @checkstack/template-engine@0.4.5
|
|
161
|
+
- @checkstack/signal-frontend@0.2.5
|
|
162
|
+
|
|
163
|
+
## 0.6.1
|
|
164
|
+
|
|
165
|
+
### Patch Changes
|
|
166
|
+
|
|
167
|
+
- @checkstack/script-packages-frontend@0.3.11
|
|
168
|
+
|
|
3
169
|
## 0.6.0
|
|
4
170
|
|
|
5
171
|
### Minor Changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@checkstack/automation-frontend",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"license": "Elastic-2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.tsx",
|
|
@@ -13,19 +13,20 @@
|
|
|
13
13
|
"lint:code": "eslint . --max-warnings 0"
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@checkstack/auth-common": "0.
|
|
17
|
-
"@checkstack/
|
|
18
|
-
"@checkstack/
|
|
19
|
-
"@checkstack/
|
|
20
|
-
"@checkstack/common": "
|
|
21
|
-
"@checkstack/
|
|
22
|
-
"@checkstack/
|
|
23
|
-
"@checkstack/
|
|
24
|
-
"@checkstack/
|
|
25
|
-
"@checkstack/
|
|
26
|
-
"@checkstack/
|
|
27
|
-
"@checkstack/
|
|
28
|
-
"@checkstack/
|
|
16
|
+
"@checkstack/auth-common": "0.10.0",
|
|
17
|
+
"@checkstack/auth-frontend": "0.8.0",
|
|
18
|
+
"@checkstack/automation-common": "0.7.0",
|
|
19
|
+
"@checkstack/ai-common": "0.5.0",
|
|
20
|
+
"@checkstack/catalog-common": "2.4.0",
|
|
21
|
+
"@checkstack/common": "0.16.0",
|
|
22
|
+
"@checkstack/frontend-api": "0.10.0",
|
|
23
|
+
"@checkstack/gitops-frontend": "0.5.8",
|
|
24
|
+
"@checkstack/integration-common": "0.9.1",
|
|
25
|
+
"@checkstack/script-packages-frontend": "0.3.12",
|
|
26
|
+
"@checkstack/secrets-frontend": "0.2.7",
|
|
27
|
+
"@checkstack/signal-frontend": "0.2.5",
|
|
28
|
+
"@checkstack/template-engine": "0.4.5",
|
|
29
|
+
"@checkstack/ui": "1.16.1",
|
|
29
30
|
"@dnd-kit/core": "^6.3.1",
|
|
30
31
|
"@dnd-kit/sortable": "^8.0.0",
|
|
31
32
|
"@dnd-kit/utilities": "^3.2.2",
|
|
@@ -39,6 +40,6 @@
|
|
|
39
40
|
"typescript": "^5.0.0",
|
|
40
41
|
"@types/react": "^19.0.0",
|
|
41
42
|
"@checkstack/tsconfig": "0.0.7",
|
|
42
|
-
"@checkstack/scripts": "0.6.
|
|
43
|
+
"@checkstack/scripts": "0.6.2"
|
|
43
44
|
}
|
|
44
45
|
}
|
|
@@ -50,6 +50,7 @@ import {
|
|
|
50
50
|
GitOpsLockBanner,
|
|
51
51
|
useProvenanceLock,
|
|
52
52
|
} from "@checkstack/gitops-frontend";
|
|
53
|
+
import { TeamOwnershipPicker, teamCreateErrorMessage } from "@checkstack/auth-frontend";
|
|
53
54
|
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
54
55
|
import { AutomationDefinitionEditor } from "../editor/AutomationDefinitionEditor";
|
|
55
56
|
import { assignDefaultIds } from "../editor/action-helpers";
|
|
@@ -112,6 +113,9 @@ const AutomationEditContent: React.FC = () => {
|
|
|
112
113
|
const { allowed: hasManageAccess } = accessApi.useAccess(
|
|
113
114
|
automationAccess.manage,
|
|
114
115
|
);
|
|
116
|
+
// allowGlobal: reuse the manage access check — global manage permission
|
|
117
|
+
// means the caller may create a resource not scoped to any team.
|
|
118
|
+
const allowGlobal = hasManageAccess;
|
|
115
119
|
|
|
116
120
|
// GitOps provenance lock: when this automation is declaratively managed,
|
|
117
121
|
// disable manual edits + show a banner. `entityId` is the automation id
|
|
@@ -139,6 +143,10 @@ const AutomationEditContent: React.FC = () => {
|
|
|
139
143
|
// "not chosen yet" — save is blocked until one is selected.
|
|
140
144
|
const [runAsApplicationId, setRunAsApplicationId] = React.useState("");
|
|
141
145
|
const [statusEnabled, setStatusEnabled] = React.useState(true);
|
|
146
|
+
// Owning team — create mode only. null means global (no team owner).
|
|
147
|
+
const [ownerTeamId, setOwnerTeamId] = React.useState<string | null>(null);
|
|
148
|
+
// Inline error for team-create failures (e.g. OWNER_TEAM_REQUIRED).
|
|
149
|
+
const [ownerTeamError, setOwnerTeamError] = React.useState<string | null>(null);
|
|
142
150
|
|
|
143
151
|
// Existing group values for the picker's "pick existing" suggestions.
|
|
144
152
|
const groupsQuery = client.listAutomationGroups.useQuery();
|
|
@@ -299,7 +307,14 @@ const AutomationEditContent: React.FC = () => {
|
|
|
299
307
|
resolveRoute(automationRoutes.routes.edit, { automationId: data.id }),
|
|
300
308
|
);
|
|
301
309
|
},
|
|
302
|
-
onError: (error) =>
|
|
310
|
+
onError: (error) => {
|
|
311
|
+
const inline = teamCreateErrorMessage(error);
|
|
312
|
+
if (inline) {
|
|
313
|
+
setOwnerTeamError(inline);
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
toast.error(extractErrorMessage(error));
|
|
317
|
+
},
|
|
303
318
|
});
|
|
304
319
|
|
|
305
320
|
const updateMutation = client.updateAutomation.useMutation({
|
|
@@ -343,6 +358,7 @@ const AutomationEditContent: React.FC = () => {
|
|
|
343
358
|
};
|
|
344
359
|
|
|
345
360
|
const handleSave = async () => {
|
|
361
|
+
setOwnerTeamError(null);
|
|
346
362
|
const committed = commitActiveTab();
|
|
347
363
|
if (!committed) return;
|
|
348
364
|
|
|
@@ -375,6 +391,7 @@ const AutomationEditContent: React.FC = () => {
|
|
|
375
391
|
runAs: runAsApplicationId,
|
|
376
392
|
status: statusEnabled ? "enabled" : "disabled",
|
|
377
393
|
definition: merged,
|
|
394
|
+
teamId: ownerTeamId ?? undefined,
|
|
378
395
|
});
|
|
379
396
|
} else if (automationId) {
|
|
380
397
|
updateMutation.mutate({
|
|
@@ -555,6 +572,19 @@ const AutomationEditContent: React.FC = () => {
|
|
|
555
572
|
disabled={!canManage}
|
|
556
573
|
showError={!!runAsError}
|
|
557
574
|
/>
|
|
575
|
+
{/* Owning team — shown only when creating a new automation */}
|
|
576
|
+
{isNew && (
|
|
577
|
+
<TeamOwnershipPicker
|
|
578
|
+
value={ownerTeamId}
|
|
579
|
+
onChange={(id) => {
|
|
580
|
+
setOwnerTeamId(id);
|
|
581
|
+
setOwnerTeamError(null);
|
|
582
|
+
}}
|
|
583
|
+
allowGlobal={allowGlobal}
|
|
584
|
+
disabled={!canManage}
|
|
585
|
+
error={ownerTeamError}
|
|
586
|
+
/>
|
|
587
|
+
)}
|
|
558
588
|
<div className="flex items-center justify-between">
|
|
559
589
|
<Label htmlFor="enabled">Enabled</Label>
|
|
560
590
|
<Toggle
|