@checkstack/auth-frontend 0.0.4 → 0.1.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 +79 -0
- package/package.json +1 -1
- package/src/components/ApplicationsTab.tsx +9 -2
- package/src/components/AuthSettingsPage.tsx +39 -14
- package/src/components/RoleDialog.tsx +6 -0
- package/src/components/TeamAccessEditor.tsx +560 -0
- package/src/components/TeamsTab.tsx +569 -0
- package/src/index.tsx +4 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,84 @@
|
|
|
1
1
|
# @checkstack/auth-frontend
|
|
2
2
|
|
|
3
|
+
## 0.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 8e43507: # Teams and Resource-Level Access Control
|
|
8
|
+
|
|
9
|
+
This release introduces a comprehensive Teams system for organizing users and controlling access to resources at a granular level.
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
### Team Management
|
|
14
|
+
|
|
15
|
+
- Create, update, and delete teams with name and description
|
|
16
|
+
- Add/remove users from teams
|
|
17
|
+
- Designate team managers with elevated privileges
|
|
18
|
+
- View team membership and manager status
|
|
19
|
+
|
|
20
|
+
### Resource-Level Access Control
|
|
21
|
+
|
|
22
|
+
- Grant teams access to specific resources (systems, health checks, incidents, maintenances)
|
|
23
|
+
- Configure read-only or manage permissions per team
|
|
24
|
+
- Resource-level "Team Only" mode that restricts access exclusively to team members
|
|
25
|
+
- Separate `resourceAccessSettings` table for resource-level settings (not per-grant)
|
|
26
|
+
- Automatic cleanup of grants when teams are deleted (database cascade)
|
|
27
|
+
|
|
28
|
+
### Middleware Integration
|
|
29
|
+
|
|
30
|
+
- Extended `autoAuthMiddleware` to support resource access checks
|
|
31
|
+
- Single-resource pre-handler validation for detail endpoints
|
|
32
|
+
- Automatic list filtering for collection endpoints
|
|
33
|
+
- S2S endpoints for access verification
|
|
34
|
+
|
|
35
|
+
### Frontend Components
|
|
36
|
+
|
|
37
|
+
- `TeamsTab` component for managing teams in Auth Settings
|
|
38
|
+
- `TeamAccessEditor` component for assigning team access to resources
|
|
39
|
+
- Resource-level "Team Only" toggle in `TeamAccessEditor`
|
|
40
|
+
- Integration into System, Health Check, Incident, and Maintenance editors
|
|
41
|
+
|
|
42
|
+
## Breaking Changes
|
|
43
|
+
|
|
44
|
+
### API Response Format Changes
|
|
45
|
+
|
|
46
|
+
List endpoints now return objects with named keys instead of arrays directly:
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
// Before
|
|
50
|
+
const systems = await catalogApi.getSystems();
|
|
51
|
+
|
|
52
|
+
// After
|
|
53
|
+
const { systems } = await catalogApi.getSystems();
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Affected endpoints:
|
|
57
|
+
|
|
58
|
+
- `catalog.getSystems` → `{ systems: [...] }`
|
|
59
|
+
- `healthcheck.getConfigurations` → `{ configurations: [...] }`
|
|
60
|
+
- `incident.listIncidents` → `{ incidents: [...] }`
|
|
61
|
+
- `maintenance.listMaintenances` → `{ maintenances: [...] }`
|
|
62
|
+
|
|
63
|
+
### User Identity Enrichment
|
|
64
|
+
|
|
65
|
+
`RealUser` and `ApplicationUser` types now include `teamIds: string[]` field with team memberships.
|
|
66
|
+
|
|
67
|
+
## Documentation
|
|
68
|
+
|
|
69
|
+
See `docs/backend/teams.md` for complete API reference and integration guide.
|
|
70
|
+
|
|
71
|
+
### Patch Changes
|
|
72
|
+
|
|
73
|
+
- 97c5a6b: Fix Radix UI accessibility warning in dialog components by adding visually hidden DialogDescription components
|
|
74
|
+
- Updated dependencies [8e43507]
|
|
75
|
+
- Updated dependencies [97c5a6b]
|
|
76
|
+
- Updated dependencies [8e43507]
|
|
77
|
+
- @checkstack/ui@0.1.0
|
|
78
|
+
- @checkstack/auth-common@0.1.0
|
|
79
|
+
- @checkstack/common@0.1.0
|
|
80
|
+
- @checkstack/frontend-api@0.0.4
|
|
81
|
+
|
|
3
82
|
## 0.0.4
|
|
4
83
|
|
|
5
84
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
ConfirmationModal,
|
|
19
19
|
Dialog,
|
|
20
20
|
DialogContent,
|
|
21
|
+
DialogDescription,
|
|
21
22
|
DialogHeader,
|
|
22
23
|
DialogTitle,
|
|
23
24
|
DialogFooter,
|
|
@@ -189,8 +190,8 @@ export const ApplicationsTab: React.FC<ApplicationsTabProps> = ({
|
|
|
189
190
|
<Alert variant="info" className="mb-4">
|
|
190
191
|
<AlertDescription>
|
|
191
192
|
External applications use API keys to authenticate with the
|
|
192
|
-
Checkstack API. The secret is only shown once when created—store
|
|
193
|
-
securely.
|
|
193
|
+
Checkstack API. The secret is only shown once when created—store
|
|
194
|
+
it securely.
|
|
194
195
|
</AlertDescription>
|
|
195
196
|
</Alert>
|
|
196
197
|
|
|
@@ -346,6 +347,9 @@ export const ApplicationsTab: React.FC<ApplicationsTabProps> = ({
|
|
|
346
347
|
<DialogTitle>
|
|
347
348
|
Application Secret: {newSecretDialog.applicationName}
|
|
348
349
|
</DialogTitle>
|
|
350
|
+
<DialogDescription className="sr-only">
|
|
351
|
+
Copy your application secret - it will only be shown once
|
|
352
|
+
</DialogDescription>
|
|
349
353
|
</DialogHeader>
|
|
350
354
|
<div className="space-y-4">
|
|
351
355
|
<Alert variant="warning">
|
|
@@ -399,6 +403,9 @@ export const ApplicationsTab: React.FC<ApplicationsTabProps> = ({
|
|
|
399
403
|
<DialogContent>
|
|
400
404
|
<DialogHeader>
|
|
401
405
|
<DialogTitle>Create Application</DialogTitle>
|
|
406
|
+
<DialogDescription className="sr-only">
|
|
407
|
+
Create a new external application with API key access
|
|
408
|
+
</DialogDescription>
|
|
402
409
|
</DialogHeader>
|
|
403
410
|
<div className="space-y-4">
|
|
404
411
|
<div>
|
|
@@ -1,21 +1,18 @@
|
|
|
1
1
|
import React, { useEffect, useState, useMemo } from "react";
|
|
2
2
|
import { useSearchParams } from "react-router-dom";
|
|
3
|
-
import {
|
|
4
|
-
useApi,
|
|
5
|
-
permissionApiRef,
|
|
6
|
-
rpcApiRef,
|
|
7
|
-
} from "@checkstack/frontend-api";
|
|
3
|
+
import { useApi, permissionApiRef, rpcApiRef } from "@checkstack/frontend-api";
|
|
8
4
|
import { PageLayout, useToast, Tabs, TabPanel } from "@checkstack/ui";
|
|
9
5
|
import { authApiRef, AuthUser, Role, AuthStrategy, Permission } from "../api";
|
|
10
6
|
import {
|
|
11
7
|
permissions as authPermissions,
|
|
12
8
|
AuthApi,
|
|
13
9
|
} from "@checkstack/auth-common";
|
|
14
|
-
import { Shield, Settings2, Users, Key } from "lucide-react";
|
|
10
|
+
import { Shield, Settings2, Users, Key, Users2 } from "lucide-react";
|
|
15
11
|
import { UsersTab } from "./UsersTab";
|
|
16
12
|
import { RolesTab } from "./RolesTab";
|
|
17
13
|
import { StrategiesTab } from "./StrategiesTab";
|
|
18
14
|
import { ApplicationsTab } from "./ApplicationsTab";
|
|
15
|
+
import { TeamsTab } from "./TeamsTab";
|
|
19
16
|
|
|
20
17
|
export const AuthSettingsPage: React.FC = () => {
|
|
21
18
|
const authApi = useApi(authApiRef);
|
|
@@ -28,7 +25,7 @@ export const AuthSettingsPage: React.FC = () => {
|
|
|
28
25
|
const session = authApi.useSession();
|
|
29
26
|
|
|
30
27
|
const [activeTab, setActiveTab] = useState<
|
|
31
|
-
"users" | "roles" | "strategies" | "applications"
|
|
28
|
+
"users" | "roles" | "teams" | "strategies" | "applications"
|
|
32
29
|
>("users");
|
|
33
30
|
const [users, setUsers] = useState<(AuthUser & { roles: string[] })[]>([]);
|
|
34
31
|
const [roles, setRoles] = useState<Role[]>([]);
|
|
@@ -44,8 +41,13 @@ export const AuthSettingsPage: React.FC = () => {
|
|
|
44
41
|
useEffect(() => {
|
|
45
42
|
const tab = searchParams.get("tab");
|
|
46
43
|
|
|
47
|
-
if (
|
|
48
|
-
|
|
44
|
+
if (
|
|
45
|
+
tab &&
|
|
46
|
+
["users", "roles", "teams", "strategies", "applications"].includes(tab)
|
|
47
|
+
) {
|
|
48
|
+
setActiveTab(
|
|
49
|
+
tab as "users" | "roles" | "teams" | "strategies" | "applications"
|
|
50
|
+
);
|
|
49
51
|
}
|
|
50
52
|
|
|
51
53
|
// Clear the URL params after processing
|
|
@@ -86,20 +88,27 @@ export const AuthSettingsPage: React.FC = () => {
|
|
|
86
88
|
const canManageApplications = permissionApi.usePermission(
|
|
87
89
|
authPermissions.applicationsManage.id
|
|
88
90
|
);
|
|
91
|
+
const canReadTeams = permissionApi.usePermission(
|
|
92
|
+
authPermissions.teamsRead.id
|
|
93
|
+
);
|
|
94
|
+
const canManageTeams = permissionApi.usePermission(
|
|
95
|
+
authPermissions.teamsManage.id
|
|
96
|
+
);
|
|
89
97
|
|
|
90
|
-
// Compute loading and permission states for PageLayout
|
|
91
98
|
const permissionsLoading =
|
|
92
99
|
loading ||
|
|
93
100
|
canReadUsers.loading ||
|
|
94
101
|
canReadRoles.loading ||
|
|
95
102
|
canManageStrategies.loading ||
|
|
96
|
-
canManageApplications.loading
|
|
103
|
+
canManageApplications.loading ||
|
|
104
|
+
canReadTeams.loading;
|
|
97
105
|
|
|
98
106
|
const hasAnyPermission =
|
|
99
107
|
canReadUsers.allowed ||
|
|
100
108
|
canReadRoles.allowed ||
|
|
101
109
|
canManageStrategies.allowed ||
|
|
102
|
-
canManageApplications.allowed
|
|
110
|
+
canManageApplications.allowed ||
|
|
111
|
+
canReadTeams.allowed;
|
|
103
112
|
|
|
104
113
|
// Special case: if user is not logged in, show permission denied
|
|
105
114
|
const isAllowed = session.data?.user ? hasAnyPermission : false;
|
|
@@ -107,7 +116,7 @@ export const AuthSettingsPage: React.FC = () => {
|
|
|
107
116
|
// Compute visible tabs based on permissions
|
|
108
117
|
const visibleTabs = useMemo(() => {
|
|
109
118
|
const tabs: Array<{
|
|
110
|
-
id: "users" | "roles" | "strategies" | "applications";
|
|
119
|
+
id: "users" | "roles" | "teams" | "strategies" | "applications";
|
|
111
120
|
label: string;
|
|
112
121
|
icon: React.ReactNode;
|
|
113
122
|
}> = [];
|
|
@@ -123,6 +132,12 @@ export const AuthSettingsPage: React.FC = () => {
|
|
|
123
132
|
label: "Roles & Permissions",
|
|
124
133
|
icon: <Shield size={18} />,
|
|
125
134
|
});
|
|
135
|
+
if (canReadTeams.allowed)
|
|
136
|
+
tabs.push({
|
|
137
|
+
id: "teams",
|
|
138
|
+
label: "Teams",
|
|
139
|
+
icon: <Users2 size={18} />,
|
|
140
|
+
});
|
|
126
141
|
if (canManageStrategies.allowed)
|
|
127
142
|
tabs.push({
|
|
128
143
|
id: "strategies",
|
|
@@ -139,6 +154,7 @@ export const AuthSettingsPage: React.FC = () => {
|
|
|
139
154
|
}, [
|
|
140
155
|
canReadUsers.allowed,
|
|
141
156
|
canReadRoles.allowed,
|
|
157
|
+
canReadTeams.allowed,
|
|
142
158
|
canManageStrategies.allowed,
|
|
143
159
|
canManageApplications.allowed,
|
|
144
160
|
]);
|
|
@@ -196,7 +212,7 @@ export const AuthSettingsPage: React.FC = () => {
|
|
|
196
212
|
activeTab={activeTab}
|
|
197
213
|
onTabChange={(tabId) =>
|
|
198
214
|
setActiveTab(
|
|
199
|
-
tabId as "users" | "roles" | "strategies" | "applications"
|
|
215
|
+
tabId as "users" | "roles" | "teams" | "strategies" | "applications"
|
|
200
216
|
)
|
|
201
217
|
}
|
|
202
218
|
className="mb-6"
|
|
@@ -229,6 +245,15 @@ export const AuthSettingsPage: React.FC = () => {
|
|
|
229
245
|
/>
|
|
230
246
|
</TabPanel>
|
|
231
247
|
|
|
248
|
+
<TabPanel id="teams" activeTab={activeTab}>
|
|
249
|
+
<TeamsTab
|
|
250
|
+
users={users}
|
|
251
|
+
canReadTeams={canReadTeams.allowed}
|
|
252
|
+
canManageTeams={canManageTeams.allowed}
|
|
253
|
+
onDataChange={fetchData}
|
|
254
|
+
/>
|
|
255
|
+
</TabPanel>
|
|
256
|
+
|
|
232
257
|
<TabPanel id="strategies" activeTab={activeTab}>
|
|
233
258
|
<StrategiesTab
|
|
234
259
|
strategies={strategies}
|
|
@@ -2,6 +2,7 @@ import React, { useState } from "react";
|
|
|
2
2
|
import {
|
|
3
3
|
Dialog,
|
|
4
4
|
DialogContent,
|
|
5
|
+
DialogDescription,
|
|
5
6
|
DialogHeader,
|
|
6
7
|
DialogTitle,
|
|
7
8
|
DialogFooter,
|
|
@@ -111,6 +112,11 @@ export const RoleDialog: React.FC<RoleDialogProps> = ({
|
|
|
111
112
|
<DialogContent className="max-w-2xl max-h-[80vh] overflow-y-auto">
|
|
112
113
|
<DialogHeader>
|
|
113
114
|
<DialogTitle>{isEditing ? "Edit Role" : "Create Role"}</DialogTitle>
|
|
115
|
+
<DialogDescription className="sr-only">
|
|
116
|
+
{isEditing
|
|
117
|
+
? "Modify the settings and permissions for this role"
|
|
118
|
+
: "Create a new role with specific permissions"}
|
|
119
|
+
</DialogDescription>
|
|
114
120
|
</DialogHeader>
|
|
115
121
|
|
|
116
122
|
<div className="space-y-4">
|