@checkstack/auth-frontend 0.1.0 → 0.2.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 CHANGED
@@ -1,5 +1,98 @@
1
1
  # @checkstack/auth-frontend
2
2
 
3
+ ## 0.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 9faec1f: # Unified AccessRule Terminology Refactoring
8
+
9
+ This release completes a comprehensive terminology refactoring from "permission" to "accessRule" across the entire codebase, establishing a consistent and modern access control vocabulary.
10
+
11
+ ## Changes
12
+
13
+ ### Core Infrastructure (`@checkstack/common`)
14
+
15
+ - Introduced `AccessRule` interface as the primary access control type
16
+ - Added `accessPair()` helper for creating read/manage access rule pairs
17
+ - Added `access()` builder for individual access rules
18
+ - Replaced `Permission` type with `AccessRule` throughout
19
+
20
+ ### API Changes
21
+
22
+ - `env.registerPermissions()` → `env.registerAccessRules()`
23
+ - `meta.permissions` → `meta.access` in RPC contracts
24
+ - `usePermission()` → `useAccess()` in frontend hooks
25
+ - Route `permission:` field → `accessRule:` field
26
+
27
+ ### UI Changes
28
+
29
+ - "Roles & Permissions" tab → "Roles & Access Rules"
30
+ - "You don't have permission..." → "You don't have access..."
31
+ - All permission-related UI text updated
32
+
33
+ ### Documentation & Templates
34
+
35
+ - Updated 18 documentation files with AccessRule terminology
36
+ - Updated 7 scaffolding templates with `accessPair()` pattern
37
+ - All code examples use new AccessRule API
38
+
39
+ ## Migration Guide
40
+
41
+ ### Backend Plugins
42
+
43
+ ```diff
44
+ - import { permissionList } from "./permissions";
45
+ - env.registerPermissions(permissionList);
46
+ + import { accessRules } from "./access";
47
+ + env.registerAccessRules(accessRules);
48
+ ```
49
+
50
+ ### RPC Contracts
51
+
52
+ ```diff
53
+ - .meta({ userType: "user", permissions: [permissions.read.id] })
54
+ + .meta({ userType: "user", access: [access.read] })
55
+ ```
56
+
57
+ ### Frontend Hooks
58
+
59
+ ```diff
60
+ - const canRead = accessApi.usePermission(permissions.read.id);
61
+ + const canRead = accessApi.useAccess(access.read);
62
+ ```
63
+
64
+ ### Routes
65
+
66
+ ```diff
67
+ - permission: permissions.entityRead.id,
68
+ + accessRule: access.read,
69
+ ```
70
+
71
+ ### Patch Changes
72
+
73
+ - 95eeec7: # Auto-login after credential registration
74
+
75
+ Users are now automatically logged in after successful registration when using the credential (email & password) authentication strategy.
76
+
77
+ ## Changes
78
+
79
+ ### Backend (`@checkstack/auth-backend`)
80
+
81
+ - Added `autoSignIn: true` to the `emailAndPassword` configuration in better-auth
82
+ - Users no longer need to manually log in after registration; a session is created immediately upon successful sign-up
83
+
84
+ ### Frontend (`@checkstack/auth-frontend`)
85
+
86
+ - Updated `RegisterPage` to use full page navigation after registration to ensure the session state refreshes correctly
87
+ - Updated `LoginPage` to use full page navigation after login to ensure fresh permissions state when switching between users
88
+
89
+ - Updated dependencies [9faec1f]
90
+ - Updated dependencies [f533141]
91
+ - @checkstack/auth-common@0.2.0
92
+ - @checkstack/common@0.2.0
93
+ - @checkstack/frontend-api@0.1.0
94
+ - @checkstack/ui@0.2.0
95
+
3
96
  ## 0.1.0
4
97
 
5
98
  ### Minor Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@checkstack/auth-frontend",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "main": "src/index.tsx",
6
6
  "exports": {
package/src/api.ts CHANGED
@@ -25,10 +25,10 @@ export interface Role {
25
25
  description?: string | null;
26
26
  isSystem?: boolean;
27
27
  isAssignable?: boolean; // False for anonymous role - not assignable to users
28
- permissions?: string[];
28
+ accessRules?: string[];
29
29
  }
30
30
 
31
- export interface Permission {
31
+ export interface AccessRuleEntry {
32
32
  id: string;
33
33
  description?: string;
34
34
  }
@@ -303,7 +303,7 @@ export const ApplicationsTab: React.FC<ApplicationsTabProps> = ({
303
303
 
304
304
  {!canManageApplications && (
305
305
  <p className="text-xs text-muted-foreground mt-4">
306
- You don't have permission to manage applications.
306
+ You don't have access to manage applications.
307
307
  </p>
308
308
  )}
309
309
  </CardContent>
@@ -1,12 +1,15 @@
1
1
  import React, { useEffect, useState, useMemo } from "react";
2
2
  import { useSearchParams } from "react-router-dom";
3
- import { useApi, permissionApiRef, rpcApiRef } from "@checkstack/frontend-api";
3
+ import { useApi, accessApiRef, rpcApiRef } from "@checkstack/frontend-api";
4
4
  import { PageLayout, useToast, Tabs, TabPanel } from "@checkstack/ui";
5
- import { authApiRef, AuthUser, Role, AuthStrategy, Permission } from "../api";
6
5
  import {
7
- permissions as authPermissions,
8
- AuthApi,
9
- } from "@checkstack/auth-common";
6
+ authApiRef,
7
+ AuthUser,
8
+ Role,
9
+ AuthStrategy,
10
+ AccessRuleEntry,
11
+ } from "../api";
12
+ import { authAccess, AuthApi } from "@checkstack/auth-common";
10
13
  import { Shield, Settings2, Users, Key, Users2 } from "lucide-react";
11
14
  import { UsersTab } from "./UsersTab";
12
15
  import { RolesTab } from "./RolesTab";
@@ -18,7 +21,7 @@ export const AuthSettingsPage: React.FC = () => {
18
21
  const authApi = useApi(authApiRef);
19
22
  const rpcApi = useApi(rpcApiRef);
20
23
  const authClient = rpcApi.forPlugin(AuthApi);
21
- const permissionApi = useApi(permissionApiRef);
24
+ const accessApi = useApi(accessApiRef);
22
25
  const toast = useToast();
23
26
  const [searchParams, setSearchParams] = useSearchParams();
24
27
 
@@ -29,13 +32,11 @@ export const AuthSettingsPage: React.FC = () => {
29
32
  >("users");
30
33
  const [users, setUsers] = useState<(AuthUser & { roles: string[] })[]>([]);
31
34
  const [roles, setRoles] = useState<Role[]>([]);
32
- const [permissions, setPermissions] = useState<Permission[]>([]);
35
+ const [accessRuleEntries, setAccessRuleEntries] = useState<AccessRuleEntry[]>([]);
33
36
  const [strategies, setStrategies] = useState<AuthStrategy[]>([]);
34
37
  const [loading, setLoading] = useState(true);
35
38
 
36
- const canReadUsers = permissionApi.usePermission(
37
- authPermissions.usersRead.id
38
- );
39
+ const canReadUsers = accessApi.useAccess(authAccess.users.read);
39
40
 
40
41
  // Handle ?tab= URL parameters (from command palette)
41
42
  useEffect(() => {
@@ -58,44 +59,20 @@ export const AuthSettingsPage: React.FC = () => {
58
59
  }
59
60
  }, [searchParams, setSearchParams]);
60
61
 
61
- const canManageUsers = permissionApi.usePermission(
62
- authPermissions.usersManage.id
63
- );
64
- const canCreateUsers = permissionApi.usePermission(
65
- authPermissions.usersCreate.id
66
- );
67
- const canReadRoles = permissionApi.usePermission(
68
- authPermissions.rolesRead.id
69
- );
70
- const canCreateRoles = permissionApi.usePermission(
71
- authPermissions.rolesCreate.id
72
- );
73
- const canUpdateRoles = permissionApi.usePermission(
74
- authPermissions.rolesUpdate.id
75
- );
76
- const canDeleteRoles = permissionApi.usePermission(
77
- authPermissions.rolesDelete.id
78
- );
79
- const canManageRoles = permissionApi.usePermission(
80
- authPermissions.rolesManage.id
81
- );
82
- const canManageStrategies = permissionApi.usePermission(
83
- authPermissions.strategiesManage.id
84
- );
85
- const canManageRegistration = permissionApi.usePermission(
86
- authPermissions.registrationManage.id
87
- );
88
- const canManageApplications = permissionApi.usePermission(
89
- authPermissions.applicationsManage.id
90
- );
91
- const canReadTeams = permissionApi.usePermission(
92
- authPermissions.teamsRead.id
93
- );
94
- const canManageTeams = permissionApi.usePermission(
95
- authPermissions.teamsManage.id
96
- );
97
-
98
- const permissionsLoading =
62
+ const canManageUsers = accessApi.useAccess(authAccess.users.manage);
63
+ const canCreateUsers = accessApi.useAccess(authAccess.users.create);
64
+ const canReadRoles = accessApi.useAccess(authAccess.roles.read);
65
+ const canCreateRoles = accessApi.useAccess(authAccess.roles.create);
66
+ const canUpdateRoles = accessApi.useAccess(authAccess.roles.update);
67
+ const canDeleteRoles = accessApi.useAccess(authAccess.roles.delete);
68
+ const canManageRoles = accessApi.useAccess(authAccess.roles.manage);
69
+ const canManageStrategies = accessApi.useAccess(authAccess.strategies);
70
+ const canManageRegistration = accessApi.useAccess(authAccess.registration);
71
+ const canManageApplications = accessApi.useAccess(authAccess.applications);
72
+ const canReadTeams = accessApi.useAccess(authAccess.teams.read);
73
+ const canManageTeams = accessApi.useAccess(authAccess.teams.manage);
74
+
75
+ const accessRulesLoading =
99
76
  loading ||
100
77
  canReadUsers.loading ||
101
78
  canReadRoles.loading ||
@@ -103,17 +80,17 @@ export const AuthSettingsPage: React.FC = () => {
103
80
  canManageApplications.loading ||
104
81
  canReadTeams.loading;
105
82
 
106
- const hasAnyPermission =
83
+ const hasAnyAccess =
107
84
  canReadUsers.allowed ||
108
85
  canReadRoles.allowed ||
109
86
  canManageStrategies.allowed ||
110
87
  canManageApplications.allowed ||
111
88
  canReadTeams.allowed;
112
89
 
113
- // Special case: if user is not logged in, show permission denied
114
- const isAllowed = session.data?.user ? hasAnyPermission : false;
90
+ // Special case: if user is not logged in, show access denied
91
+ const isAllowed = session.data?.user ? hasAnyAccess : false;
115
92
 
116
- // Compute visible tabs based on permissions
93
+ // Compute visible tabs based on access rules
117
94
  const visibleTabs = useMemo(() => {
118
95
  const tabs: Array<{
119
96
  id: "users" | "roles" | "teams" | "strategies" | "applications";
@@ -129,7 +106,7 @@ export const AuthSettingsPage: React.FC = () => {
129
106
  if (canReadRoles.allowed)
130
107
  tabs.push({
131
108
  id: "roles",
132
- label: "Roles & Permissions",
109
+ label: "Roles & Access Rules",
133
110
  icon: <Shield size={18} />,
134
111
  });
135
112
  if (canReadTeams.allowed)
@@ -176,11 +153,11 @@ export const AuthSettingsPage: React.FC = () => {
176
153
  roles: string[];
177
154
  })[];
178
155
  const rolesData = await authClient.getRoles();
179
- const permissionsData = await authClient.getPermissions();
156
+ const accessRulesData = await authClient.getAccessRules();
180
157
  const strategiesData = await authClient.getStrategies();
181
158
  setUsers(usersData);
182
159
  setRoles(rolesData);
183
- setPermissions(permissionsData);
160
+ setAccessRuleEntries(accessRulesData);
184
161
  setStrategies(strategiesData);
185
162
  } catch (error: unknown) {
186
163
  const message =
@@ -204,7 +181,7 @@ export const AuthSettingsPage: React.FC = () => {
204
181
  return (
205
182
  <PageLayout
206
183
  title="Authentication Settings"
207
- loading={permissionsLoading}
184
+ loading={accessRulesLoading}
208
185
  allowed={isAllowed}
209
186
  >
210
187
  <Tabs
@@ -235,7 +212,7 @@ export const AuthSettingsPage: React.FC = () => {
235
212
  <TabPanel id="roles" activeTab={activeTab}>
236
213
  <RolesTab
237
214
  roles={roles}
238
- permissions={permissions}
215
+ accessRulesList={accessRuleEntries}
239
216
  userRoleIds={currentUserRoleIds}
240
217
  canReadRoles={canReadRoles.allowed}
241
218
  canCreateRoles={canCreateRoles.allowed}
@@ -1,5 +1,5 @@
1
1
  import React, { useState } from "react";
2
- import { Link, useNavigate } from "react-router-dom";
2
+ import { Link } from "react-router-dom";
3
3
  import { LogIn, LogOut, AlertCircle } from "lucide-react";
4
4
  import {
5
5
  useApi,
@@ -38,7 +38,7 @@ import {
38
38
  } from "@checkstack/ui";
39
39
  import { authApiRef } from "../api";
40
40
  import { useEnabledStrategies } from "../hooks/useEnabledStrategies";
41
- import { usePermissions } from "../hooks/usePermissions";
41
+ import { useAccessRules } from "../hooks/useAccessRules";
42
42
  import { useAuthClient } from "../lib/auth-client";
43
43
  import { SocialProviderButton } from "./SocialProviderButton";
44
44
  import { useEffect } from "react";
@@ -47,7 +47,7 @@ export const LoginPage = () => {
47
47
  const [email, setEmail] = useState("");
48
48
  const [password, setPassword] = useState("");
49
49
  const [loading, setLoading] = useState(false);
50
- const navigate = useNavigate();
50
+
51
51
  const authApi = useApi(authApiRef);
52
52
  const rpcApi = useApi(rpcApiRef);
53
53
  const authRpcClient = rpcApi.forPlugin(AuthApi);
@@ -74,7 +74,8 @@ export const LoginPage = () => {
74
74
  if (error) {
75
75
  console.error("Login failed:", error);
76
76
  } else {
77
- navigate("/");
77
+ // Use full page navigation to ensure session/permissions state refreshes
78
+ globalThis.location.href = "/";
78
79
  }
79
80
  } finally {
80
81
  setLoading(false);
@@ -273,7 +274,7 @@ export const LogoutMenuItem = (_props: UserMenuItemsContext) => {
273
274
  export const LoginNavbarAction = () => {
274
275
  const authApi = useApi(authApiRef);
275
276
  const { data: session, isPending } = authApi.useSession();
276
- const { permissions, loading: permissionsLoading } = usePermissions();
277
+ const { accessRules, loading: accessRulesLoading } = useAccessRules();
277
278
  const authClient = useAuthClient();
278
279
  const [hasCredentialAccount, setHasCredentialAccount] =
279
280
  useState<boolean>(false);
@@ -295,7 +296,7 @@ export const LoginNavbarAction = () => {
295
296
  });
296
297
  }, [session?.user, authClient]);
297
298
 
298
- if (isPending || permissionsLoading || credentialLoading) {
299
+ if (isPending || accessRulesLoading || credentialLoading) {
299
300
  return <div className="w-20 h-9 bg-muted animate-pulse rounded-full" />;
300
301
  }
301
302
 
@@ -306,7 +307,7 @@ export const LoginNavbarAction = () => {
306
307
  );
307
308
  const hasBottomItems = bottomExtensions.length > 0;
308
309
  const menuContext: UserMenuItemsContext = {
309
- permissions,
310
+ accessRules,
310
311
  hasCredentialAccount,
311
312
  };
312
313
 
@@ -1,13 +1,9 @@
1
1
  import React, { useState, useEffect } from "react";
2
- import { Link, useNavigate } from "react-router-dom";
2
+ import { Link } from "react-router-dom";
3
3
  import { AlertCircle } from "lucide-react";
4
4
  import { useApi, rpcApiRef } from "@checkstack/frontend-api";
5
5
  import { authApiRef } from "../api";
6
- import {
7
- AuthApi,
8
- authRoutes,
9
- passwordSchema,
10
- } from "@checkstack/auth-common";
6
+ import { AuthApi, authRoutes, passwordSchema } from "@checkstack/auth-common";
11
7
  import { resolveRoute } from "@checkstack/common";
12
8
  import {
13
9
  Button,
@@ -35,7 +31,7 @@ export const RegisterPage = () => {
35
31
  const [password, setPassword] = useState("");
36
32
  const [loading, setLoading] = useState(false);
37
33
  const [validationErrors, setValidationErrors] = useState<string[]>([]);
38
- const navigate = useNavigate();
34
+
39
35
  const authApi = useApi(authApiRef);
40
36
  const rpcApi = useApi(rpcApiRef);
41
37
  const authRpcClient = rpcApi.forPlugin(AuthApi);
@@ -87,7 +83,8 @@ export const RegisterPage = () => {
87
83
  if (res.error) {
88
84
  console.error("Registration failed:", res.error);
89
85
  } else {
90
- navigate("/");
86
+ // Use full page navigation to ensure session state refreshes in navbar
87
+ globalThis.location.href = "/";
91
88
  }
92
89
  } catch (error) {
93
90
  console.error("Registration failed:", error);
@@ -19,20 +19,20 @@ import {
19
19
  AlertDescription,
20
20
  } from "@checkstack/ui";
21
21
  import { Check } from "lucide-react";
22
- import type { Role, Permission } from "../api";
22
+ import type { Role, AccessRuleEntry } from "../api";
23
23
 
24
24
  interface RoleDialogProps {
25
25
  open: boolean;
26
26
  onOpenChange: (open: boolean) => void;
27
27
  role?: Role;
28
- permissions: Permission[];
29
- /** Whether current user has this role (prevents permission elevation) */
28
+ accessRulesList: AccessRuleEntry[];
29
+ /** Whether current user has this role (prevents access elevation) */
30
30
  isUserRole?: boolean;
31
31
  onSave: (params: {
32
32
  id?: string;
33
33
  name: string;
34
34
  description?: string;
35
- permissions: string[];
35
+ accessRules: string[];
36
36
  }) => Promise<void>;
37
37
  }
38
38
 
@@ -40,14 +40,14 @@ export const RoleDialog: React.FC<RoleDialogProps> = ({
40
40
  open,
41
41
  onOpenChange,
42
42
  role,
43
- permissions,
43
+ accessRulesList,
44
44
  isUserRole = false,
45
45
  onSave,
46
46
  }) => {
47
47
  const [name, setName] = useState(role?.name || "");
48
48
  const [description, setDescription] = useState(role?.description || "");
49
- const [selectedPermissions, setSelectedPermissions] = useState<Set<string>>(
50
- new Set(role?.permissions || [])
49
+ const [selectedAccessRules, setSelectedAccessRules] = useState<Set<string>>(
50
+ new Set(role?.accessRules || [])
51
51
  );
52
52
  const [saving, setSaving] = useState(false);
53
53
 
@@ -55,32 +55,32 @@ export const RoleDialog: React.FC<RoleDialogProps> = ({
55
55
  React.useEffect(() => {
56
56
  setName(role?.name || "");
57
57
  setDescription(role?.description || "");
58
- setSelectedPermissions(new Set(role?.permissions || []));
58
+ setSelectedAccessRules(new Set(role?.accessRules || []));
59
59
  }, [role]);
60
60
 
61
61
  const isEditing = !!role;
62
62
  const isAdminRole = role?.id === "admin";
63
- // Disable permissions for admin (wildcard) or user's own roles (prevent elevation)
64
- const permissionsDisabled = isAdminRole || isUserRole;
63
+ // Disable access rules for admin (wildcard) or user's own roles (prevent elevation)
64
+ const accessRulesDisabled = isAdminRole || isUserRole;
65
65
 
66
- // Group permissions by plugin
67
- const permissionsByPlugin: Record<string, Permission[]> = {};
68
- for (const perm of permissions) {
66
+ // Group access rules by plugin
67
+ const accessRulesByPlugin: Record<string, AccessRuleEntry[]> = {};
68
+ for (const perm of accessRulesList) {
69
69
  const [plugin] = perm.id.split(".");
70
- if (!permissionsByPlugin[plugin]) {
71
- permissionsByPlugin[plugin] = [];
70
+ if (!accessRulesByPlugin[plugin]) {
71
+ accessRulesByPlugin[plugin] = [];
72
72
  }
73
- permissionsByPlugin[plugin].push(perm);
73
+ accessRulesByPlugin[plugin].push(perm);
74
74
  }
75
75
 
76
- const handleTogglePermission = (permissionId: string) => {
77
- const newSelected = new Set(selectedPermissions);
78
- if (newSelected.has(permissionId)) {
79
- newSelected.delete(permissionId);
76
+ const handleToggleAccessRule = (accessRuleId: string) => {
77
+ const newSelected = new Set(selectedAccessRules);
78
+ if (newSelected.has(accessRuleId)) {
79
+ newSelected.delete(accessRuleId);
80
80
  } else {
81
- newSelected.add(permissionId);
81
+ newSelected.add(accessRuleId);
82
82
  }
83
- setSelectedPermissions(newSelected);
83
+ setSelectedAccessRules(newSelected);
84
84
  };
85
85
 
86
86
  const handleSave = async () => {
@@ -90,7 +90,7 @@ export const RoleDialog: React.FC<RoleDialogProps> = ({
90
90
  ...(isEditing && { id: role.id }),
91
91
  name,
92
92
  description: description || undefined,
93
- permissions: [...selectedPermissions],
93
+ accessRules: [...selectedAccessRules],
94
94
  });
95
95
  onOpenChange(false);
96
96
  } catch (error) {
@@ -114,8 +114,8 @@ export const RoleDialog: React.FC<RoleDialogProps> = ({
114
114
  <DialogTitle>{isEditing ? "Edit Role" : "Create Role"}</DialogTitle>
115
115
  <DialogDescription className="sr-only">
116
116
  {isEditing
117
- ? "Modify the settings and permissions for this role"
118
- : "Create a new role with specific permissions"}
117
+ ? "Modify the settings and access rules for this role"
118
+ : "Create a new role with specific access rules"}
119
119
  </DialogDescription>
120
120
  </DialogHeader>
121
121
 
@@ -141,15 +141,15 @@ export const RoleDialog: React.FC<RoleDialogProps> = ({
141
141
  </div>
142
142
 
143
143
  <div>
144
- <Label className="text-base">Permissions</Label>
144
+ <Label className="text-base">Access Rules</Label>
145
145
  <p className="text-sm text-muted-foreground mt-1 mb-3">
146
- Select permissions to grant to this role. Permissions are
146
+ Select access rules to grant to this role. Access rules are
147
147
  organized by plugin.
148
148
  </p>
149
149
  {isAdminRole && (
150
150
  <Alert variant="info" className="mb-3">
151
151
  <AlertDescription>
152
- The administrator role has wildcard access to all permissions.
152
+ The administrator role has wildcard access to all access rules.
153
153
  These cannot be modified.
154
154
  </AlertDescription>
155
155
  </Alert>
@@ -157,7 +157,7 @@ export const RoleDialog: React.FC<RoleDialogProps> = ({
157
157
  {!isAdminRole && isUserRole && (
158
158
  <Alert variant="info" className="mb-3">
159
159
  <AlertDescription>
160
- You cannot modify permissions for a role you currently have.
160
+ You cannot modify access rules for a role you currently have.
161
161
  This prevents accidental self-lockout from the system.
162
162
  </AlertDescription>
163
163
  </Alert>
@@ -165,10 +165,10 @@ export const RoleDialog: React.FC<RoleDialogProps> = ({
165
165
  <div className="border rounded-lg">
166
166
  <Accordion
167
167
  type="multiple"
168
- defaultValue={Object.keys(permissionsByPlugin)}
168
+ defaultValue={Object.keys(accessRulesByPlugin)}
169
169
  className="w-full"
170
170
  >
171
- {Object.entries(permissionsByPlugin).map(([plugin, perms]) => (
171
+ {Object.entries(accessRulesByPlugin).map(([plugin, perms]) => (
172
172
  <AccordionItem key={plugin} value={plugin}>
173
173
  <AccordionTrigger className="px-4 hover:no-underline">
174
174
  <div className="flex items-center justify-between flex-1 pr-2">
@@ -177,7 +177,7 @@ export const RoleDialog: React.FC<RoleDialogProps> = ({
177
177
  </span>
178
178
  <span className="text-xs text-muted-foreground">
179
179
  {
180
- perms.filter((p) => selectedPermissions.has(p.id))
180
+ perms.filter((p) => selectedAccessRules.has(p.id))
181
181
  .length
182
182
  }{" "}
183
183
  / {perms.length} selected
@@ -187,15 +187,15 @@ export const RoleDialog: React.FC<RoleDialogProps> = ({
187
187
  <AccordionContent className="px-4">
188
188
  <div
189
189
  className={`space-y-${
190
- permissionsDisabled ? "2" : "3"
190
+ accessRulesDisabled ? "2" : "3"
191
191
  } pt-2`}
192
192
  >
193
193
  {perms.map((perm) => {
194
194
  const isAssigned =
195
- isAdminRole || selectedPermissions.has(perm.id);
195
+ isAdminRole || selectedAccessRules.has(perm.id);
196
196
 
197
- // Use view-style design when permissions are disabled
198
- if (permissionsDisabled) {
197
+ // Use view-style design when access rules are disabled
198
+ if (accessRulesDisabled) {
199
199
  return (
200
200
  <div
201
201
  key={perm.id}
@@ -239,7 +239,7 @@ export const RoleDialog: React.FC<RoleDialogProps> = ({
239
239
  );
240
240
  }
241
241
 
242
- // Use editable checkbox design when permissions are editable
242
+ // Use editable checkbox design when access rules are editable
243
243
  return (
244
244
  <div
245
245
  key={perm.id}
@@ -247,9 +247,9 @@ export const RoleDialog: React.FC<RoleDialogProps> = ({
247
247
  >
248
248
  <Checkbox
249
249
  id={`perm-${perm.id}`}
250
- checked={selectedPermissions.has(perm.id)}
250
+ checked={selectedAccessRules.has(perm.id)}
251
251
  onCheckedChange={() =>
252
- handleTogglePermission(perm.id)
252
+ handleToggleAccessRule(perm.id)
253
253
  }
254
254
  className="mt-0.5"
255
255
  />
@@ -19,12 +19,12 @@ import { Plus, Edit, Trash2 } from "lucide-react";
19
19
  import { useApi } from "@checkstack/frontend-api";
20
20
  import { rpcApiRef } from "@checkstack/frontend-api";
21
21
  import { AuthApi } from "@checkstack/auth-common";
22
- import type { Role, Permission } from "../api";
22
+ import type { Role, AccessRuleEntry } from "../api";
23
23
  import { RoleDialog } from "./RoleDialog";
24
24
 
25
25
  export interface RolesTabProps {
26
26
  roles: Role[];
27
- permissions: Permission[];
27
+ accessRulesList: AccessRuleEntry[];
28
28
  userRoleIds: string[];
29
29
  canReadRoles: boolean;
30
30
  canCreateRoles: boolean;
@@ -35,7 +35,7 @@ export interface RolesTabProps {
35
35
 
36
36
  export const RolesTab: React.FC<RolesTabProps> = ({
37
37
  roles,
38
- permissions,
38
+ accessRulesList,
39
39
  userRoleIds,
40
40
  canReadRoles,
41
41
  canCreateRoles,
@@ -65,7 +65,7 @@ export const RolesTab: React.FC<RolesTabProps> = ({
65
65
  id?: string;
66
66
  name: string;
67
67
  description?: string;
68
- permissions: string[];
68
+ accessRules: string[];
69
69
  }) => {
70
70
  try {
71
71
  if (params.id) {
@@ -73,14 +73,14 @@ export const RolesTab: React.FC<RolesTabProps> = ({
73
73
  id: params.id,
74
74
  name: params.name,
75
75
  description: params.description,
76
- permissions: params.permissions,
76
+ accessRules: params.accessRules,
77
77
  });
78
78
  toast.success("Role updated successfully");
79
79
  } else {
80
80
  await authClient.createRole({
81
81
  name: params.name,
82
82
  description: params.description,
83
- permissions: params.permissions,
83
+ accessRules: params.accessRules,
84
84
  });
85
85
  toast.success("Role created successfully");
86
86
  }
@@ -128,7 +128,7 @@ export const RolesTab: React.FC<RolesTabProps> = ({
128
128
  <TableHeader>
129
129
  <TableRow>
130
130
  <TableHead>Role</TableHead>
131
- <TableHead>Permissions</TableHead>
131
+ <TableHead>Access Rules</TableHead>
132
132
  <TableHead className="text-right">Actions</TableHead>
133
133
  </TableRow>
134
134
  </TableHeader>
@@ -159,7 +159,7 @@ export const RolesTab: React.FC<RolesTabProps> = ({
159
159
  </TableCell>
160
160
  <TableCell>
161
161
  <span className="text-sm text-muted-foreground">
162
- {role.permissions?.length || 0} permissions
162
+ {role.accessRules?.length || 0} access rules
163
163
  </span>
164
164
  </TableCell>
165
165
  <TableCell className="text-right">
@@ -192,7 +192,7 @@ export const RolesTab: React.FC<RolesTabProps> = ({
192
192
  )
193
193
  ) : (
194
194
  <p className="text-muted-foreground">
195
- You don't have permission to view roles.
195
+ You don't have access to view roles.
196
196
  </p>
197
197
  )}
198
198
  </CardContent>
@@ -202,7 +202,7 @@ export const RolesTab: React.FC<RolesTabProps> = ({
202
202
  open={roleDialogOpen}
203
203
  onOpenChange={setRoleDialogOpen}
204
204
  role={editingRole}
205
- permissions={permissions}
205
+ accessRulesList={accessRulesList}
206
206
  isUserRole={editingRole ? userRoleIds.includes(editingRole.id) : false}
207
207
  onSave={handleSaveRole}
208
208
  />
@@ -64,7 +64,7 @@ export const StrategiesTab: React.FC<StrategiesTabProps> = ({
64
64
  setStrategyConfigs(configs);
65
65
  }, [strategies]);
66
66
 
67
- // Fetch registration data when we have permission
67
+ // Fetch registration data when we have access
68
68
  useEffect(() => {
69
69
  if (!canManageRegistration) {
70
70
  setLoadingRegistration(false);
@@ -206,7 +206,7 @@ export const StrategiesTab: React.FC<StrategiesTabProps> = ({
206
206
  )
207
207
  ) : (
208
208
  <p className="text-sm text-muted-foreground">
209
- You don't have permission to manage registration settings.
209
+ You don't have access to manage registration settings.
210
210
  </p>
211
211
  )}
212
212
  </CardContent>
@@ -268,7 +268,7 @@ export const StrategiesTab: React.FC<StrategiesTabProps> = ({
268
268
 
269
269
  {!canManageStrategies && (
270
270
  <p className="text-xs text-muted-foreground mt-4">
271
- You don't have permission to manage strategies.
271
+ You don't have access to manage strategies.
272
272
  </p>
273
273
  )}
274
274
  </div>
@@ -26,11 +26,8 @@ import {
26
26
  Settings,
27
27
  Lock,
28
28
  } from "lucide-react";
29
- import { useApi, rpcApiRef, permissionApiRef } from "@checkstack/frontend-api";
30
- import {
31
- AuthApi,
32
- permissions as authPermissions,
33
- } from "@checkstack/auth-common";
29
+ import { useApi, rpcApiRef, accessApiRef } from "@checkstack/frontend-api";
30
+ import { AuthApi, authAccess } from "@checkstack/auth-common";
34
31
 
35
32
  interface TeamAccess {
36
33
  teamId: string;
@@ -72,12 +69,12 @@ export const TeamAccessEditor: React.FC<TeamAccessEditorProps> = ({
72
69
  onChange,
73
70
  }) => {
74
71
  const rpcApi = useApi(rpcApiRef);
75
- const permissionApi = useApi(permissionApiRef);
72
+ const accessApi = useApi(accessApiRef);
76
73
  const authClient = rpcApi.forPlugin(AuthApi);
77
74
  const toast = useToast();
78
75
 
79
- const { allowed: canManageTeams } = permissionApi.usePermission(
80
- authPermissions.teamsManage.id
76
+ const { allowed: canManageTeams } = accessApi.useAccess(
77
+ authAccess.teams.manage
81
78
  );
82
79
 
83
80
  const [expanded, setExpanded] = useState(initialExpanded);
@@ -270,7 +267,7 @@ export const TeamAccessEditor: React.FC<TeamAccessEditorProps> = ({
270
267
  Team Only
271
268
  </Label>
272
269
  <span className="text-xs text-muted-foreground">
273
- (Bypass global permissions)
270
+ (Bypass global accesss)
274
271
  </span>
275
272
  </div>
276
273
  <Toggle
@@ -320,7 +317,7 @@ export const TeamAccessEditor: React.FC<TeamAccessEditorProps> = ({
320
317
  <div className="space-y-2">
321
318
  {accessList.length === 0 ? (
322
319
  <p className="text-sm text-muted-foreground text-center py-2">
323
- No team restrictions. All users with permission can access.
320
+ No team restrictions. All users with access can access.
324
321
  </p>
325
322
  ) : (
326
323
  accessList.map((access) => (
@@ -472,7 +469,7 @@ export const TeamAccessEditor: React.FC<TeamAccessEditorProps> = ({
472
469
  </Label>
473
470
  <p className="text-xs text-muted-foreground">
474
471
  When enabled, only team members can access (global
475
- permissions bypassed)
472
+ access bypassed)
476
473
  </p>
477
474
  </div>
478
475
  </div>
@@ -488,7 +485,7 @@ export const TeamAccessEditor: React.FC<TeamAccessEditorProps> = ({
488
485
  {accessList.length === 0 ? (
489
486
  <p className="text-sm text-muted-foreground text-center py-4 bg-muted/30 rounded-lg">
490
487
  No team restrictions configured. All users with appropriate
491
- permissions can access this resource.
488
+ access can view this resource.
492
489
  </p>
493
490
  ) : (
494
491
  <div className="border rounded-lg divide-y">
@@ -370,7 +370,7 @@ export const TeamsTab: React.FC<TeamsTabProps> = ({
370
370
  )
371
371
  ) : (
372
372
  <p className="text-muted-foreground">
373
- You don't have permission to view teams.
373
+ You don't have access to view teams.
374
374
  </p>
375
375
  )}
376
376
  </CardContent>
@@ -130,7 +130,7 @@ export const UsersTab: React.FC<UsersTabProps> = ({
130
130
  <AlertDescription>
131
131
  You cannot modify roles for your own account. This security
132
132
  measure prevents accidental self-lockout from the system and
133
- permission elevation.
133
+ access elevation.
134
134
  </AlertDescription>
135
135
  </Alert>
136
136
  {canReadUsers ? (
@@ -210,7 +210,7 @@ export const UsersTab: React.FC<UsersTabProps> = ({
210
210
  )
211
211
  ) : (
212
212
  <p className="text-muted-foreground">
213
- You don't have permission to list users.
213
+ You don't have access to list users.
214
214
  </p>
215
215
  )}
216
216
  </CardContent>
@@ -3,10 +3,10 @@ import { useAuthClient } from "../lib/auth-client";
3
3
  import { rpcApiRef, useApi } from "@checkstack/frontend-api";
4
4
  import { AuthApi } from "@checkstack/auth-common";
5
5
 
6
- export const usePermissions = () => {
6
+ export const useAccessRules = () => {
7
7
  const authClient = useAuthClient();
8
8
  const { data: session, isPending: sessionPending } = authClient.useSession();
9
- const [permissions, setPermissions] = useState<string[]>([]);
9
+ const [accessRules, setAccessRules] = useState<string[]>([]);
10
10
  const [loading, setLoading] = useState(true);
11
11
  const rpcApi = useApi(rpcApiRef);
12
12
 
@@ -18,26 +18,26 @@ export const usePermissions = () => {
18
18
  }
19
19
 
20
20
  if (!session?.user) {
21
- setPermissions([]);
21
+ setAccessRules([]);
22
22
  setLoading(false);
23
23
  return;
24
24
  }
25
25
 
26
- const fetchPermissions = async () => {
26
+ const fetchAccessRules = async () => {
27
27
  try {
28
28
  const authRpc = rpcApi.forPlugin(AuthApi);
29
- const data = await authRpc.permissions();
30
- if (Array.isArray(data.permissions)) {
31
- setPermissions(data.permissions);
29
+ const data = await authRpc.accessRules();
30
+ if (Array.isArray(data.accessRules)) {
31
+ setAccessRules(data.accessRules);
32
32
  }
33
33
  } catch (error) {
34
- console.error("Failed to fetch permissions", error);
34
+ console.error("Failed to fetch access rules", error);
35
35
  } finally {
36
36
  setLoading(false);
37
37
  }
38
38
  };
39
- fetchPermissions();
39
+ fetchAccessRules();
40
40
  }, [session?.user?.id, sessionPending, rpcApi]);
41
41
 
42
- return { permissions, loading };
42
+ return { accessRules, loading };
43
43
  };
@@ -1,95 +1,141 @@
1
1
  import { describe, it, expect, mock, beforeEach } from "bun:test";
2
2
  import { authPlugin } from "./index";
3
- import { permissionApiRef } from "@checkstack/frontend-api";
4
- import { usePermissions } from "./hooks/usePermissions";
3
+ import { accessApiRef } from "@checkstack/frontend-api";
4
+ import type { AccessRule } from "@checkstack/common";
5
+ import { useAccessRules } from "./hooks/useAccessRules";
5
6
 
6
- // Mock the usePermissions hook
7
- mock.module("./hooks/usePermissions", () => ({
8
- usePermissions: mock(),
7
+ // Mock the useAccessRules hook
8
+ mock.module("./hooks/useAccessRules", () => ({
9
+ useAccessRules: mock(),
9
10
  }));
10
11
 
11
- describe("AuthPermissionApi", () => {
12
- let permissionApi: {
13
- usePermission: (p: string) => { loading: boolean; allowed: boolean };
12
+ // Test access rule objects
13
+ const testReadAccess: AccessRule = {
14
+ id: "test.read",
15
+ resource: "test",
16
+ level: "read",
17
+ description: "Test read access",
18
+ };
19
+
20
+ const testManageAccess: AccessRule = {
21
+ id: "test.manage",
22
+ resource: "test",
23
+ level: "manage",
24
+ description: "Test manage access",
25
+ };
26
+
27
+ const otherAccess: AccessRule = {
28
+ id: "other.read",
29
+ resource: "other",
30
+ level: "read",
31
+ description: "Other read access",
32
+ };
33
+
34
+ describe("AuthAccessApi", () => {
35
+ let accessApi: {
36
+ useAccess: (p: AccessRule) => { loading: boolean; allowed: boolean };
14
37
  };
15
38
 
16
39
  beforeEach(() => {
17
- const apiDef = authPlugin.apis?.find(
18
- (a) => a.ref.id === permissionApiRef.id
19
- );
20
- if (!apiDef) throw new Error("Permission API not found in plugin");
21
- permissionApi = apiDef.factory({ get: () => ({}) } as any) as any;
40
+ const apiDef = authPlugin.apis?.find((a) => a.ref.id === accessApiRef.id);
41
+ if (!apiDef) throw new Error("Access API not found in plugin");
42
+ accessApi = apiDef.factory({ get: () => ({}) } as any) as any;
22
43
  });
23
44
 
24
- it("should return true if user has the permission", () => {
25
- (usePermissions as ReturnType<typeof mock>).mockReturnValue({
26
- permissions: ["test.permission"],
45
+ it("should return true if user has the access rule", () => {
46
+ (useAccessRules as ReturnType<typeof mock>).mockReturnValue({
47
+ accessRules: ["test.read"],
27
48
  loading: false,
28
49
  });
29
50
 
30
- expect(permissionApi.usePermission("test.permission")).toEqual({
51
+ expect(accessApi.useAccess(testReadAccess)).toEqual({
31
52
  loading: false,
32
53
  allowed: true,
33
54
  });
34
55
  });
35
56
 
36
- it("should return false if user is missing the permission", () => {
37
- (usePermissions as ReturnType<typeof mock>).mockReturnValue({
38
- permissions: ["other.permission"],
57
+ it("should return false if user is missing the access rule", () => {
58
+ (useAccessRules as ReturnType<typeof mock>).mockReturnValue({
59
+ accessRules: ["other.read"],
39
60
  loading: false,
40
61
  });
41
62
 
42
- expect(permissionApi.usePermission("test.permission")).toEqual({
63
+ expect(accessApi.useAccess(testReadAccess)).toEqual({
43
64
  loading: false,
44
65
  allowed: false,
45
66
  });
46
67
  });
47
68
 
48
- it("should return false if no session data (no permissions)", () => {
49
- (usePermissions as ReturnType<typeof mock>).mockReturnValue({
50
- permissions: [],
69
+ it("should return false if no session data (no access rules)", () => {
70
+ (useAccessRules as ReturnType<typeof mock>).mockReturnValue({
71
+ accessRules: [],
51
72
  loading: false,
52
73
  });
53
74
 
54
- expect(permissionApi.usePermission("test.permission")).toEqual({
75
+ expect(accessApi.useAccess(testReadAccess)).toEqual({
55
76
  loading: false,
56
77
  allowed: false,
57
78
  });
58
79
  });
59
80
 
60
- it("should return false if no user permissions (empty array)", () => {
61
- (usePermissions as ReturnType<typeof mock>).mockReturnValue({
62
- permissions: [],
81
+ it("should return false if no user access rules (empty array)", () => {
82
+ (useAccessRules as ReturnType<typeof mock>).mockReturnValue({
83
+ accessRules: [],
63
84
  loading: false,
64
85
  });
65
86
 
66
- expect(permissionApi.usePermission("test.permission")).toEqual({
87
+ expect(accessApi.useAccess(testReadAccess)).toEqual({
67
88
  loading: false,
68
89
  allowed: false,
69
90
  });
70
91
  });
71
92
 
72
- it("should return true if user has the wildcard permission", () => {
73
- (usePermissions as ReturnType<typeof mock>).mockReturnValue({
74
- permissions: ["*"],
93
+ it("should return true if user has the wildcard access rule", () => {
94
+ (useAccessRules as ReturnType<typeof mock>).mockReturnValue({
95
+ accessRules: ["*"],
75
96
  loading: false,
76
97
  });
77
98
 
78
- expect(permissionApi.usePermission("any.permission")).toEqual({
99
+ expect(accessApi.useAccess(otherAccess)).toEqual({
79
100
  loading: false,
80
101
  allowed: true,
81
102
  });
82
103
  });
83
104
 
84
- it("should return loading state if permissions are loading", () => {
85
- (usePermissions as ReturnType<typeof mock>).mockReturnValue({
86
- permissions: [],
105
+ it("should return true if user has manage access for a manage check", () => {
106
+ (useAccessRules as ReturnType<typeof mock>).mockReturnValue({
107
+ accessRules: ["test.manage"],
108
+ loading: false,
109
+ });
110
+
111
+ expect(accessApi.useAccess(testManageAccess)).toEqual({
112
+ loading: false,
113
+ allowed: true,
114
+ });
115
+ });
116
+
117
+ it("should return loading state if access rules are loading", () => {
118
+ (useAccessRules as ReturnType<typeof mock>).mockReturnValue({
119
+ accessRules: [],
87
120
  loading: true,
88
121
  });
89
122
 
90
- expect(permissionApi.usePermission("test.permission")).toEqual({
123
+ expect(accessApi.useAccess(testReadAccess)).toEqual({
91
124
  loading: true,
92
125
  allowed: false,
93
126
  });
94
127
  });
128
+
129
+ it("should return true if user has manage access for a read check", () => {
130
+ (useAccessRules as ReturnType<typeof mock>).mockReturnValue({
131
+ accessRules: ["test.manage"],
132
+ loading: false,
133
+ });
134
+
135
+ // User has test.manage, which implies test.read
136
+ expect(accessApi.useAccess(testReadAccess)).toEqual({
137
+ loading: false,
138
+ allowed: true,
139
+ });
140
+ });
95
141
  });
package/src/index.tsx CHANGED
@@ -1,8 +1,8 @@
1
1
  import React from "react";
2
2
  import {
3
3
  ApiRef,
4
- permissionApiRef,
5
- PermissionApi,
4
+ accessApiRef,
5
+ AccessApi,
6
6
  createFrontendPlugin,
7
7
  createSlotExtension,
8
8
  NavbarRightSlot,
@@ -22,71 +22,52 @@ import { ChangePasswordPage } from "./components/ChangePasswordPage";
22
22
  import { authApiRef, AuthApi, AuthSession } from "./api";
23
23
  import { getAuthClientLazy } from "./lib/auth-client";
24
24
 
25
- import { usePermissions } from "./hooks/usePermissions";
25
+ import { useAccessRules } from "./hooks/useAccessRules";
26
26
 
27
- import { PermissionAction, qualifyPermissionId } from "@checkstack/common";
27
+ import type { AccessRule } from "@checkstack/common";
28
28
  import { useNavigate } from "react-router-dom";
29
29
  import { Settings2, Key } from "lucide-react";
30
30
  import { DropdownMenuItem } from "@checkstack/ui";
31
31
  import { UserMenuItemsContext } from "@checkstack/frontend-api";
32
32
  import { AuthSettingsPage } from "./components/AuthSettingsPage";
33
33
  import {
34
- permissions as authPermissions,
34
+ authAccess,
35
35
  authRoutes,
36
36
  pluginMetadata,
37
37
  } from "@checkstack/auth-common";
38
38
  import { resolveRoute } from "@checkstack/common";
39
39
 
40
- class AuthPermissionApi implements PermissionApi {
41
- usePermission(permission: string): { loading: boolean; allowed: boolean } {
42
- const { permissions, loading } = usePermissions();
43
-
44
- if (loading) {
45
- return { loading: true, allowed: false };
46
- }
47
-
48
- // If no user, or user has no permissions, return false
49
- if (!permissions || permissions.length === 0) {
50
- return { loading: false, allowed: false };
51
- }
52
- const allowed =
53
- permissions.includes("*") || permissions.includes(permission);
54
- return { loading: false, allowed };
55
- }
56
-
57
- useResourcePermission(
58
- resource: string,
59
- action: PermissionAction
60
- ): { loading: boolean; allowed: boolean } {
61
- const { permissions, loading } = usePermissions();
40
+ /**
41
+ * Unified access API implementation.
42
+ * Uses AccessRule objects for access checks.
43
+ */
44
+ class AuthAccessApi implements AccessApi {
45
+ useAccess(accessRule: AccessRule): { loading: boolean; allowed: boolean } {
46
+ const { accessRules, loading } = useAccessRules();
62
47
 
63
48
  if (loading) {
64
49
  return { loading: true, allowed: false };
65
50
  }
66
51
 
67
- if (!permissions || permissions.length === 0) {
52
+ // If no user, or user has no access rules, return false
53
+ if (!accessRules || accessRules.length === 0) {
68
54
  return { loading: false, allowed: false };
69
55
  }
70
56
 
71
- const isWildcard = permissions.includes("*");
72
- const hasResourceManage = permissions.includes(`${resource}.manage`);
73
- const hasSpecificPermission = permissions.includes(`${resource}.${action}`);
57
+ const accessRuleId = accessRule.id;
74
58
 
75
- // manage implies read
76
- const isAllowed =
77
- isWildcard ||
78
- hasResourceManage ||
79
- (action === "read" && hasResourceManage) ||
80
- hasSpecificPermission;
59
+ // Check wildcard, exact match, or manage implies read
60
+ const isWildcard = accessRules.includes("*");
61
+ const hasExact = accessRules.includes(accessRuleId);
81
62
 
82
- return { loading: false, allowed: isAllowed };
83
- }
63
+ // For read actions, also check if user has manage access for the same resource
64
+ const hasManage =
65
+ accessRule.level === "read"
66
+ ? accessRules.includes(`${accessRule.resource}.manage`)
67
+ : false;
84
68
 
85
- useManagePermission(resource: string): {
86
- loading: boolean;
87
- allowed: boolean;
88
- } {
89
- return this.useResourcePermission(resource, "manage");
69
+ const allowed = isWildcard || hasExact || hasManage;
70
+ return { loading: false, allowed };
90
71
  }
91
72
  }
92
73
 
@@ -180,8 +161,8 @@ export const authPlugin = createFrontendPlugin({
180
161
  factory: () => new BetterAuthApi(),
181
162
  },
182
163
  {
183
- ref: permissionApiRef as ApiRef<unknown>,
184
- factory: () => new AuthPermissionApi(),
164
+ ref: accessApiRef as ApiRef<unknown>,
165
+ factory: () => new AuthAccessApi(),
185
166
  },
186
167
  ],
187
168
  routes: [
@@ -222,12 +203,9 @@ export const authPlugin = createFrontendPlugin({
222
203
  },
223
204
  createSlotExtension(UserMenuItemsSlot, {
224
205
  id: "auth.user-menu.settings",
225
- component: ({ permissions: userPerms }: UserMenuItemsContext) => {
206
+ component: ({ accessRules: userPerms }: UserMenuItemsContext) => {
226
207
  const navigate = useNavigate();
227
- const qualifiedId = qualifyPermissionId(
228
- pluginMetadata,
229
- authPermissions.strategiesManage
230
- );
208
+ const qualifiedId = `${pluginMetadata.pluginId}.${authAccess.strategies.id}`;
231
209
  const canManage =
232
210
  userPerms.includes("*") || userPerms.includes(qualifiedId);
233
211