@checkstack/auth-frontend 0.1.0 → 0.3.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.
@@ -1,12 +1,19 @@
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";
4
- import { PageLayout, useToast, Tabs, TabPanel } from "@checkstack/ui";
5
- import { authApiRef, AuthUser, Role, AuthStrategy, Permission } from "../api";
6
3
  import {
7
- permissions as authPermissions,
8
- AuthApi,
9
- } from "@checkstack/auth-common";
4
+ useApi,
5
+ accessApiRef,
6
+ usePluginClient,
7
+ } from "@checkstack/frontend-api";
8
+ import { PageLayout, Tabs, TabPanel } from "@checkstack/ui";
9
+ import {
10
+ authApiRef,
11
+ AuthUser,
12
+ Role,
13
+ AuthStrategy,
14
+ AccessRuleEntry,
15
+ } from "../api";
16
+ import { authAccess, AuthApi } from "@checkstack/auth-common";
10
17
  import { Shield, Settings2, Users, Key, Users2 } from "lucide-react";
11
18
  import { UsersTab } from "./UsersTab";
12
19
  import { RolesTab } from "./RolesTab";
@@ -16,10 +23,8 @@ import { TeamsTab } from "./TeamsTab";
16
23
 
17
24
  export const AuthSettingsPage: React.FC = () => {
18
25
  const authApi = useApi(authApiRef);
19
- const rpcApi = useApi(rpcApiRef);
20
- const authClient = rpcApi.forPlugin(AuthApi);
21
- const permissionApi = useApi(permissionApiRef);
22
- const toast = useToast();
26
+ const authClient = usePluginClient(AuthApi);
27
+ const accessApi = useApi(accessApiRef);
23
28
  const [searchParams, setSearchParams] = useSearchParams();
24
29
 
25
30
  const session = authApi.useSession();
@@ -27,15 +32,8 @@ export const AuthSettingsPage: React.FC = () => {
27
32
  const [activeTab, setActiveTab] = useState<
28
33
  "users" | "roles" | "teams" | "strategies" | "applications"
29
34
  >("users");
30
- const [users, setUsers] = useState<(AuthUser & { roles: string[] })[]>([]);
31
- const [roles, setRoles] = useState<Role[]>([]);
32
- const [permissions, setPermissions] = useState<Permission[]>([]);
33
- const [strategies, setStrategies] = useState<AuthStrategy[]>([]);
34
- const [loading, setLoading] = useState(true);
35
35
 
36
- const canReadUsers = permissionApi.usePermission(
37
- authPermissions.usersRead.id
38
- );
36
+ const canReadUsers = accessApi.useAccess(authAccess.users.read);
39
37
 
40
38
  // Handle ?tab= URL parameters (from command palette)
41
39
  useEffect(() => {
@@ -58,62 +56,69 @@ export const AuthSettingsPage: React.FC = () => {
58
56
  }
59
57
  }, [searchParams, setSearchParams]);
60
58
 
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
59
+ const canManageUsers = accessApi.useAccess(authAccess.users.manage);
60
+ const canCreateUsers = accessApi.useAccess(authAccess.users.create);
61
+ const canReadRoles = accessApi.useAccess(authAccess.roles.read);
62
+ const canCreateRoles = accessApi.useAccess(authAccess.roles.create);
63
+ const canUpdateRoles = accessApi.useAccess(authAccess.roles.update);
64
+ const canDeleteRoles = accessApi.useAccess(authAccess.roles.delete);
65
+ const canManageRoles = accessApi.useAccess(authAccess.roles.manage);
66
+ const canManageStrategies = accessApi.useAccess(authAccess.strategies);
67
+ const canManageRegistration = accessApi.useAccess(authAccess.registration);
68
+ const canManageApplications = accessApi.useAccess(authAccess.applications);
69
+ const canReadTeams = accessApi.useAccess(authAccess.teams.read);
70
+ const canManageTeams = accessApi.useAccess(authAccess.teams.manage);
71
+
72
+ // Queries: Fetch data from API
73
+ const {
74
+ data: users = [],
75
+ isLoading: usersLoading,
76
+ refetch: refetchUsers,
77
+ } = authClient.getUsers.useQuery({}, { enabled: canReadUsers.allowed });
78
+
79
+ const {
80
+ data: roles = [],
81
+ isLoading: rolesLoading,
82
+ refetch: refetchRoles,
83
+ } = authClient.getRoles.useQuery({}, { enabled: canReadRoles.allowed });
84
+
85
+ const {
86
+ data: accessRuleEntries = [],
87
+ isLoading: rulesLoading,
88
+ refetch: refetchRules,
89
+ } = authClient.getAccessRules.useQuery({}, { enabled: canReadRoles.allowed });
90
+
91
+ const {
92
+ data: strategies = [],
93
+ isLoading: strategiesLoading,
94
+ refetch: refetchStrategies,
95
+ } = authClient.getStrategies.useQuery(
96
+ {},
97
+ { enabled: canManageStrategies.allowed }
96
98
  );
97
99
 
98
- const permissionsLoading =
99
- loading ||
100
+ const dataLoading =
101
+ usersLoading || rolesLoading || rulesLoading || strategiesLoading;
102
+
103
+ const accessRulesLoading =
104
+ dataLoading ||
100
105
  canReadUsers.loading ||
101
106
  canReadRoles.loading ||
102
107
  canManageStrategies.loading ||
103
108
  canManageApplications.loading ||
104
109
  canReadTeams.loading;
105
110
 
106
- const hasAnyPermission =
111
+ const hasAnyAccess =
107
112
  canReadUsers.allowed ||
108
113
  canReadRoles.allowed ||
109
114
  canManageStrategies.allowed ||
110
115
  canManageApplications.allowed ||
111
116
  canReadTeams.allowed;
112
117
 
113
- // Special case: if user is not logged in, show permission denied
114
- const isAllowed = session.data?.user ? hasAnyPermission : false;
118
+ // Special case: if user is not logged in, show access denied
119
+ const isAllowed = session.data?.user ? hasAnyAccess : false;
115
120
 
116
- // Compute visible tabs based on permissions
121
+ // Compute visible tabs based on access rules
117
122
  const visibleTabs = useMemo(() => {
118
123
  const tabs: Array<{
119
124
  id: "users" | "roles" | "teams" | "strategies" | "applications";
@@ -129,7 +134,7 @@ export const AuthSettingsPage: React.FC = () => {
129
134
  if (canReadRoles.allowed)
130
135
  tabs.push({
131
136
  id: "roles",
132
- label: "Roles & Permissions",
137
+ label: "Roles & Access Rules",
133
138
  icon: <Shield size={18} />,
134
139
  });
135
140
  if (canReadTeams.allowed)
@@ -169,42 +174,24 @@ export const AuthSettingsPage: React.FC = () => {
169
174
  }
170
175
  }, [visibleTabs, activeTab]);
171
176
 
172
- const fetchData = async () => {
173
- setLoading(true);
174
- try {
175
- const usersData = (await authClient.getUsers()) as (AuthUser & {
176
- roles: string[];
177
- })[];
178
- const rolesData = await authClient.getRoles();
179
- const permissionsData = await authClient.getPermissions();
180
- const strategiesData = await authClient.getStrategies();
181
- setUsers(usersData);
182
- setRoles(rolesData);
183
- setPermissions(permissionsData);
184
- setStrategies(strategiesData);
185
- } catch (error: unknown) {
186
- const message =
187
- error instanceof Error ? error.message : "Failed to fetch data";
188
- toast.error(message);
189
- console.error(error);
190
- } finally {
191
- setLoading(false);
192
- }
177
+ const handleDataChange = async () => {
178
+ await Promise.all([
179
+ refetchUsers(),
180
+ refetchRoles(),
181
+ refetchRules(),
182
+ refetchStrategies(),
183
+ ]);
193
184
  };
194
185
 
195
- // Initial data fetch
196
- useEffect(() => {
197
- fetchData();
198
- }, []);
199
-
200
186
  // Get current user's role IDs for the RolesTab
187
+ const typedUsers = users as (AuthUser & { roles: string[] })[];
201
188
  const currentUserRoleIds =
202
- users.find((u) => u.id === session.data?.user?.id)?.roles || [];
189
+ typedUsers.find((u) => u.id === session.data?.user?.id)?.roles || [];
203
190
 
204
191
  return (
205
192
  <PageLayout
206
193
  title="Authentication Settings"
207
- loading={permissionsLoading}
194
+ loading={accessRulesLoading}
208
195
  allowed={isAllowed}
209
196
  >
210
197
  <Tabs
@@ -220,52 +207,52 @@ export const AuthSettingsPage: React.FC = () => {
220
207
 
221
208
  <TabPanel id="users" activeTab={activeTab}>
222
209
  <UsersTab
223
- users={users}
224
- roles={roles}
225
- strategies={strategies}
210
+ users={typedUsers}
211
+ roles={roles as Role[]}
212
+ strategies={strategies as AuthStrategy[]}
226
213
  currentUserId={session.data?.user?.id}
227
214
  canReadUsers={canReadUsers.allowed}
228
215
  canCreateUsers={canCreateUsers.allowed}
229
216
  canManageUsers={canManageUsers.allowed}
230
217
  canManageRoles={canManageRoles.allowed}
231
- onDataChange={fetchData}
218
+ onDataChange={handleDataChange}
232
219
  />
233
220
  </TabPanel>
234
221
 
235
222
  <TabPanel id="roles" activeTab={activeTab}>
236
223
  <RolesTab
237
- roles={roles}
238
- permissions={permissions}
224
+ roles={roles as Role[]}
225
+ accessRulesList={accessRuleEntries as AccessRuleEntry[]}
239
226
  userRoleIds={currentUserRoleIds}
240
227
  canReadRoles={canReadRoles.allowed}
241
228
  canCreateRoles={canCreateRoles.allowed}
242
229
  canUpdateRoles={canUpdateRoles.allowed}
243
230
  canDeleteRoles={canDeleteRoles.allowed}
244
- onDataChange={fetchData}
231
+ onDataChange={handleDataChange}
245
232
  />
246
233
  </TabPanel>
247
234
 
248
235
  <TabPanel id="teams" activeTab={activeTab}>
249
236
  <TeamsTab
250
- users={users}
237
+ users={typedUsers}
251
238
  canReadTeams={canReadTeams.allowed}
252
239
  canManageTeams={canManageTeams.allowed}
253
- onDataChange={fetchData}
240
+ onDataChange={handleDataChange}
254
241
  />
255
242
  </TabPanel>
256
243
 
257
244
  <TabPanel id="strategies" activeTab={activeTab}>
258
245
  <StrategiesTab
259
- strategies={strategies}
246
+ strategies={strategies as AuthStrategy[]}
260
247
  canManageStrategies={canManageStrategies.allowed}
261
248
  canManageRegistration={canManageRegistration.allowed}
262
- onDataChange={fetchData}
249
+ onDataChange={handleDataChange}
263
250
  />
264
251
  </TabPanel>
265
252
 
266
253
  <TabPanel id="applications" activeTab={activeTab}>
267
254
  <ApplicationsTab
268
- roles={roles}
255
+ roles={roles as Role[]}
269
256
  canManageApplications={canManageApplications.allowed}
270
257
  />
271
258
  </TabPanel>
@@ -1,11 +1,11 @@
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,
6
6
  ExtensionSlot,
7
7
  pluginRegistry,
8
- rpcApiRef,
8
+ usePluginClient,
9
9
  UserMenuItemsSlot,
10
10
  UserMenuItemsBottomSlot,
11
11
  UserMenuItemsContext,
@@ -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,24 +47,16 @@ 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
- const rpcApi = useApi(rpcApiRef);
53
- const authRpcClient = rpcApi.forPlugin(AuthApi);
52
+ const authClient = usePluginClient(AuthApi);
54
53
  const { strategies, loading: strategiesLoading } = useEnabledStrategies();
55
- const [registrationAllowed, setRegistrationAllowed] = useState<boolean>(true);
56
54
 
57
- useEffect(() => {
58
- authRpcClient
59
- .getRegistrationStatus()
60
- .then(({ allowRegistration }) => {
61
- setRegistrationAllowed(allowRegistration);
62
- })
63
- .catch((error: Error) => {
64
- console.error("Failed to check registration status:", error);
65
- setRegistrationAllowed(true);
66
- });
67
- }, [authRpcClient]);
55
+ // Query: Registration status
56
+ const { data: registrationData } = authClient.getRegistrationStatus.useQuery(
57
+ {}
58
+ );
59
+ const registrationAllowed = registrationData?.allowRegistration ?? true;
68
60
 
69
61
  const handleCredentialLogin = async (e: React.FormEvent) => {
70
62
  e.preventDefault();
@@ -74,7 +66,8 @@ export const LoginPage = () => {
74
66
  if (error) {
75
67
  console.error("Login failed:", error);
76
68
  } else {
77
- navigate("/");
69
+ // Use full page navigation to ensure session/permissions state refreshes
70
+ globalThis.location.href = "/";
78
71
  }
79
72
  } finally {
80
73
  setLoading(false);
@@ -273,7 +266,7 @@ export const LogoutMenuItem = (_props: UserMenuItemsContext) => {
273
266
  export const LoginNavbarAction = () => {
274
267
  const authApi = useApi(authApiRef);
275
268
  const { data: session, isPending } = authApi.useSession();
276
- const { permissions, loading: permissionsLoading } = usePermissions();
269
+ const { accessRules, loading: accessRulesLoading } = useAccessRules();
277
270
  const authClient = useAuthClient();
278
271
  const [hasCredentialAccount, setHasCredentialAccount] =
279
272
  useState<boolean>(false);
@@ -295,7 +288,7 @@ export const LoginNavbarAction = () => {
295
288
  });
296
289
  }, [session?.user, authClient]);
297
290
 
298
- if (isPending || permissionsLoading || credentialLoading) {
291
+ if (isPending || accessRulesLoading || credentialLoading) {
299
292
  return <div className="w-20 h-9 bg-muted animate-pulse rounded-full" />;
300
293
  }
301
294
 
@@ -306,7 +299,7 @@ export const LoginNavbarAction = () => {
306
299
  );
307
300
  const hasBottomItems = bottomExtensions.length > 0;
308
301
  const menuContext: UserMenuItemsContext = {
309
- permissions,
302
+ accessRules,
310
303
  hasCredentialAccount,
311
304
  };
312
305
 
@@ -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
- import { useApi, rpcApiRef } from "@checkstack/frontend-api";
4
+ import { useApi, usePluginClient } 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,14 +31,11 @@ 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
- const rpcApi = useApi(rpcApiRef);
41
- const authRpcClient = rpcApi.forPlugin(AuthApi);
36
+ const authClient = usePluginClient(AuthApi);
42
37
  const { strategies, loading: strategiesLoading } = useEnabledStrategies();
43
- const [registrationAllowed, setRegistrationAllowed] = useState<boolean>(true);
44
- const [checkingRegistration, setCheckingRegistration] = useState(true);
45
- const authClient = useAuthClient();
38
+ const authBetterClient = useAuthClient();
46
39
 
47
40
  // Validate password on change
48
41
  useEffect(() => {
@@ -58,19 +51,10 @@ export const RegisterPage = () => {
58
51
  }
59
52
  }, [password]);
60
53
 
61
- useEffect(() => {
62
- authRpcClient
63
- .getRegistrationStatus()
64
- .then(({ allowRegistration }) => {
65
- setRegistrationAllowed(allowRegistration);
66
- })
67
- .catch((error: Error) => {
68
- console.error("Failed to check registration status:", error);
69
- // Default to allowed on error to avoid blocking
70
- setRegistrationAllowed(true);
71
- })
72
- .finally(() => setCheckingRegistration(false));
73
- }, [authRpcClient]);
54
+ // Query: Registration status
55
+ const { data: registrationData, isLoading: checkingRegistration } =
56
+ authClient.getRegistrationStatus.useQuery({});
57
+ const registrationAllowed = registrationData?.allowRegistration ?? true;
74
58
 
75
59
  const handleCredentialRegister = async (e: React.FormEvent) => {
76
60
  e.preventDefault();
@@ -83,11 +67,16 @@ export const RegisterPage = () => {
83
67
 
84
68
  setLoading(true);
85
69
  try {
86
- const res = await authClient.signUp.email({ name, email, password });
70
+ const res = await authBetterClient.signUp.email({
71
+ name,
72
+ email,
73
+ password,
74
+ });
87
75
  if (res.error) {
88
76
  console.error("Registration failed:", res.error);
89
77
  } else {
90
- navigate("/");
78
+ // Use full page navigation to ensure session state refreshes in navbar
79
+ globalThis.location.href = "/";
91
80
  }
92
81
  } catch (error) {
93
82
  console.error("Registration failed:", error);