@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.
- package/CHANGELOG.md +155 -0
- package/package.json +1 -1
- package/src/api.ts +2 -2
- package/src/components/ApplicationsTab.tsx +89 -90
- package/src/components/AuthSettingsPage.tsx +87 -100
- package/src/components/LoginPage.tsx +15 -22
- package/src/components/RegisterPage.tsx +17 -28
- package/src/components/RoleDialog.tsx +49 -53
- package/src/components/RolesTab.tsx +60 -47
- package/src/components/StrategiesTab.tsx +90 -96
- package/src/components/TeamAccessEditor.tsx +131 -123
- package/src/components/TeamsTab.tsx +180 -162
- package/src/components/UsersTab.tsx +43 -32
- package/src/hooks/useAccessRules.ts +32 -0
- package/src/hooks/useEnabledStrategies.ts +10 -41
- package/src/index.test.tsx +83 -37
- package/src/index.tsx +29 -51
- package/src/lib/auth-client.ts +17 -22
- package/src/hooks/usePermissions.ts +0 -43
|
@@ -19,20 +19,20 @@ import {
|
|
|
19
19
|
AlertDescription,
|
|
20
20
|
} from "@checkstack/ui";
|
|
21
21
|
import { Check } from "lucide-react";
|
|
22
|
-
import type { Role,
|
|
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
|
-
|
|
29
|
-
/** Whether current user has this role (prevents
|
|
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
|
-
|
|
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
|
-
|
|
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 [
|
|
50
|
-
new Set(role?.
|
|
49
|
+
const [selectedAccessRules, setSelectedAccessRules] = useState<Set<string>>(
|
|
50
|
+
new Set(role?.accessRules || [])
|
|
51
51
|
);
|
|
52
52
|
const [saving, setSaving] = useState(false);
|
|
53
53
|
|
|
@@ -55,49 +55,45 @@ export const RoleDialog: React.FC<RoleDialogProps> = ({
|
|
|
55
55
|
React.useEffect(() => {
|
|
56
56
|
setName(role?.name || "");
|
|
57
57
|
setDescription(role?.description || "");
|
|
58
|
-
|
|
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
|
|
64
|
-
const
|
|
63
|
+
// Disable access rules for admin (wildcard) or user's own roles (prevent elevation)
|
|
64
|
+
const accessRulesDisabled = isAdminRole || isUserRole;
|
|
65
65
|
|
|
66
|
-
// Group
|
|
67
|
-
const
|
|
68
|
-
for (const perm of
|
|
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 (!
|
|
71
|
-
|
|
70
|
+
if (!accessRulesByPlugin[plugin]) {
|
|
71
|
+
accessRulesByPlugin[plugin] = [];
|
|
72
72
|
}
|
|
73
|
-
|
|
73
|
+
accessRulesByPlugin[plugin].push(perm);
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
const
|
|
77
|
-
const newSelected = new Set(
|
|
78
|
-
if (newSelected.has(
|
|
79
|
-
newSelected.delete(
|
|
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(
|
|
81
|
+
newSelected.add(accessRuleId);
|
|
82
82
|
}
|
|
83
|
-
|
|
83
|
+
setSelectedAccessRules(newSelected);
|
|
84
84
|
};
|
|
85
85
|
|
|
86
|
-
const handleSave =
|
|
86
|
+
const handleSave = () => {
|
|
87
87
|
setSaving(true);
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
console.error("Failed to save role:", error);
|
|
98
|
-
} finally {
|
|
99
|
-
setSaving(false);
|
|
100
|
-
}
|
|
88
|
+
onSave({
|
|
89
|
+
...(isEditing && { id: role.id }),
|
|
90
|
+
name,
|
|
91
|
+
description: description || undefined,
|
|
92
|
+
accessRules: [...selectedAccessRules],
|
|
93
|
+
});
|
|
94
|
+
// Dialog closing and saving state are managed by the parent via onDataChange callback
|
|
95
|
+
onOpenChange(false);
|
|
96
|
+
setSaving(false);
|
|
101
97
|
};
|
|
102
98
|
|
|
103
99
|
let buttonText = "Create";
|
|
@@ -114,8 +110,8 @@ export const RoleDialog: React.FC<RoleDialogProps> = ({
|
|
|
114
110
|
<DialogTitle>{isEditing ? "Edit Role" : "Create Role"}</DialogTitle>
|
|
115
111
|
<DialogDescription className="sr-only">
|
|
116
112
|
{isEditing
|
|
117
|
-
? "Modify the settings and
|
|
118
|
-
: "Create a new role with specific
|
|
113
|
+
? "Modify the settings and access rules for this role"
|
|
114
|
+
: "Create a new role with specific access rules"}
|
|
119
115
|
</DialogDescription>
|
|
120
116
|
</DialogHeader>
|
|
121
117
|
|
|
@@ -141,23 +137,23 @@ export const RoleDialog: React.FC<RoleDialogProps> = ({
|
|
|
141
137
|
</div>
|
|
142
138
|
|
|
143
139
|
<div>
|
|
144
|
-
<Label className="text-base">
|
|
140
|
+
<Label className="text-base">Access Rules</Label>
|
|
145
141
|
<p className="text-sm text-muted-foreground mt-1 mb-3">
|
|
146
|
-
Select
|
|
142
|
+
Select access rules to grant to this role. Access rules are
|
|
147
143
|
organized by plugin.
|
|
148
144
|
</p>
|
|
149
145
|
{isAdminRole && (
|
|
150
146
|
<Alert variant="info" className="mb-3">
|
|
151
147
|
<AlertDescription>
|
|
152
|
-
The administrator role has wildcard access to all
|
|
153
|
-
These cannot be modified.
|
|
148
|
+
The administrator role has wildcard access to all access
|
|
149
|
+
rules. These cannot be modified.
|
|
154
150
|
</AlertDescription>
|
|
155
151
|
</Alert>
|
|
156
152
|
)}
|
|
157
153
|
{!isAdminRole && isUserRole && (
|
|
158
154
|
<Alert variant="info" className="mb-3">
|
|
159
155
|
<AlertDescription>
|
|
160
|
-
You cannot modify
|
|
156
|
+
You cannot modify access rules for a role you currently have.
|
|
161
157
|
This prevents accidental self-lockout from the system.
|
|
162
158
|
</AlertDescription>
|
|
163
159
|
</Alert>
|
|
@@ -165,10 +161,10 @@ export const RoleDialog: React.FC<RoleDialogProps> = ({
|
|
|
165
161
|
<div className="border rounded-lg">
|
|
166
162
|
<Accordion
|
|
167
163
|
type="multiple"
|
|
168
|
-
defaultValue={Object.keys(
|
|
164
|
+
defaultValue={Object.keys(accessRulesByPlugin)}
|
|
169
165
|
className="w-full"
|
|
170
166
|
>
|
|
171
|
-
{Object.entries(
|
|
167
|
+
{Object.entries(accessRulesByPlugin).map(([plugin, perms]) => (
|
|
172
168
|
<AccordionItem key={plugin} value={plugin}>
|
|
173
169
|
<AccordionTrigger className="px-4 hover:no-underline">
|
|
174
170
|
<div className="flex items-center justify-between flex-1 pr-2">
|
|
@@ -177,7 +173,7 @@ export const RoleDialog: React.FC<RoleDialogProps> = ({
|
|
|
177
173
|
</span>
|
|
178
174
|
<span className="text-xs text-muted-foreground">
|
|
179
175
|
{
|
|
180
|
-
perms.filter((p) =>
|
|
176
|
+
perms.filter((p) => selectedAccessRules.has(p.id))
|
|
181
177
|
.length
|
|
182
178
|
}{" "}
|
|
183
179
|
/ {perms.length} selected
|
|
@@ -187,15 +183,15 @@ export const RoleDialog: React.FC<RoleDialogProps> = ({
|
|
|
187
183
|
<AccordionContent className="px-4">
|
|
188
184
|
<div
|
|
189
185
|
className={`space-y-${
|
|
190
|
-
|
|
186
|
+
accessRulesDisabled ? "2" : "3"
|
|
191
187
|
} pt-2`}
|
|
192
188
|
>
|
|
193
189
|
{perms.map((perm) => {
|
|
194
190
|
const isAssigned =
|
|
195
|
-
isAdminRole ||
|
|
191
|
+
isAdminRole || selectedAccessRules.has(perm.id);
|
|
196
192
|
|
|
197
|
-
// Use view-style design when
|
|
198
|
-
if (
|
|
193
|
+
// Use view-style design when access rules are disabled
|
|
194
|
+
if (accessRulesDisabled) {
|
|
199
195
|
return (
|
|
200
196
|
<div
|
|
201
197
|
key={perm.id}
|
|
@@ -239,7 +235,7 @@ export const RoleDialog: React.FC<RoleDialogProps> = ({
|
|
|
239
235
|
);
|
|
240
236
|
}
|
|
241
237
|
|
|
242
|
-
// Use editable checkbox design when
|
|
238
|
+
// Use editable checkbox design when access rules are editable
|
|
243
239
|
return (
|
|
244
240
|
<div
|
|
245
241
|
key={perm.id}
|
|
@@ -247,9 +243,9 @@ export const RoleDialog: React.FC<RoleDialogProps> = ({
|
|
|
247
243
|
>
|
|
248
244
|
<Checkbox
|
|
249
245
|
id={`perm-${perm.id}`}
|
|
250
|
-
checked={
|
|
246
|
+
checked={selectedAccessRules.has(perm.id)}
|
|
251
247
|
onCheckedChange={() =>
|
|
252
|
-
|
|
248
|
+
handleToggleAccessRule(perm.id)
|
|
253
249
|
}
|
|
254
250
|
className="mt-0.5"
|
|
255
251
|
/>
|
|
@@ -16,15 +16,14 @@ import {
|
|
|
16
16
|
useToast,
|
|
17
17
|
} from "@checkstack/ui";
|
|
18
18
|
import { Plus, Edit, Trash2 } from "lucide-react";
|
|
19
|
-
import {
|
|
20
|
-
import { rpcApiRef } from "@checkstack/frontend-api";
|
|
19
|
+
import { usePluginClient } from "@checkstack/frontend-api";
|
|
21
20
|
import { AuthApi } from "@checkstack/auth-common";
|
|
22
|
-
import type { Role,
|
|
21
|
+
import type { Role, AccessRuleEntry } from "../api";
|
|
23
22
|
import { RoleDialog } from "./RoleDialog";
|
|
24
23
|
|
|
25
24
|
export interface RolesTabProps {
|
|
26
25
|
roles: Role[];
|
|
27
|
-
|
|
26
|
+
accessRulesList: AccessRuleEntry[];
|
|
28
27
|
userRoleIds: string[];
|
|
29
28
|
canReadRoles: boolean;
|
|
30
29
|
canCreateRoles: boolean;
|
|
@@ -35,7 +34,7 @@ export interface RolesTabProps {
|
|
|
35
34
|
|
|
36
35
|
export const RolesTab: React.FC<RolesTabProps> = ({
|
|
37
36
|
roles,
|
|
38
|
-
|
|
37
|
+
accessRulesList,
|
|
39
38
|
userRoleIds,
|
|
40
39
|
canReadRoles,
|
|
41
40
|
canCreateRoles,
|
|
@@ -43,14 +42,51 @@ export const RolesTab: React.FC<RolesTabProps> = ({
|
|
|
43
42
|
canDeleteRoles,
|
|
44
43
|
onDataChange,
|
|
45
44
|
}) => {
|
|
46
|
-
const
|
|
47
|
-
const authClient = rpcApi.forPlugin(AuthApi);
|
|
45
|
+
const authClient = usePluginClient(AuthApi);
|
|
48
46
|
const toast = useToast();
|
|
49
47
|
|
|
50
48
|
const [roleToDelete, setRoleToDelete] = useState<string>();
|
|
51
49
|
const [roleDialogOpen, setRoleDialogOpen] = useState(false);
|
|
52
50
|
const [editingRole, setEditingRole] = useState<Role | undefined>();
|
|
53
51
|
|
|
52
|
+
// Mutations
|
|
53
|
+
const createRoleMutation = authClient.createRole.useMutation({
|
|
54
|
+
onSuccess: () => {
|
|
55
|
+
toast.success("Role created successfully");
|
|
56
|
+
void onDataChange();
|
|
57
|
+
},
|
|
58
|
+
onError: (error) => {
|
|
59
|
+
toast.error(
|
|
60
|
+
error instanceof Error ? error.message : "Failed to create role"
|
|
61
|
+
);
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const updateRoleMutation = authClient.updateRole.useMutation({
|
|
66
|
+
onSuccess: () => {
|
|
67
|
+
toast.success("Role updated successfully");
|
|
68
|
+
void onDataChange();
|
|
69
|
+
},
|
|
70
|
+
onError: (error) => {
|
|
71
|
+
toast.error(
|
|
72
|
+
error instanceof Error ? error.message : "Failed to update role"
|
|
73
|
+
);
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const deleteRoleMutation = authClient.deleteRole.useMutation({
|
|
78
|
+
onSuccess: () => {
|
|
79
|
+
toast.success("Role deleted successfully");
|
|
80
|
+
setRoleToDelete(undefined);
|
|
81
|
+
void onDataChange();
|
|
82
|
+
},
|
|
83
|
+
onError: (error) => {
|
|
84
|
+
toast.error(
|
|
85
|
+
error instanceof Error ? error.message : "Failed to delete role"
|
|
86
|
+
);
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
|
|
54
90
|
const handleCreateRole = () => {
|
|
55
91
|
setEditingRole(undefined);
|
|
56
92
|
setRoleDialogOpen(true);
|
|
@@ -65,46 +101,23 @@ export const RolesTab: React.FC<RolesTabProps> = ({
|
|
|
65
101
|
id?: string;
|
|
66
102
|
name: string;
|
|
67
103
|
description?: string;
|
|
68
|
-
|
|
104
|
+
accessRules: string[];
|
|
69
105
|
}) => {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
}
|
|
80
|
-
await authClient.createRole({
|
|
81
|
-
name: params.name,
|
|
82
|
-
description: params.description,
|
|
83
|
-
permissions: params.permissions,
|
|
84
|
-
});
|
|
85
|
-
toast.success("Role created successfully");
|
|
86
|
-
}
|
|
87
|
-
await onDataChange();
|
|
88
|
-
} catch (error: unknown) {
|
|
89
|
-
toast.error(
|
|
90
|
-
error instanceof Error ? error.message : "Failed to save role"
|
|
91
|
-
);
|
|
92
|
-
throw error;
|
|
93
|
-
}
|
|
106
|
+
await (params.id ? updateRoleMutation.mutateAsync({
|
|
107
|
+
id: params.id,
|
|
108
|
+
name: params.name,
|
|
109
|
+
description: params.description,
|
|
110
|
+
accessRules: params.accessRules,
|
|
111
|
+
}) : createRoleMutation.mutateAsync({
|
|
112
|
+
name: params.name,
|
|
113
|
+
description: params.description,
|
|
114
|
+
accessRules: params.accessRules,
|
|
115
|
+
}));
|
|
94
116
|
};
|
|
95
117
|
|
|
96
|
-
const handleDeleteRole =
|
|
118
|
+
const handleDeleteRole = () => {
|
|
97
119
|
if (!roleToDelete) return;
|
|
98
|
-
|
|
99
|
-
await authClient.deleteRole(roleToDelete);
|
|
100
|
-
toast.success("Role deleted successfully");
|
|
101
|
-
setRoleToDelete(undefined);
|
|
102
|
-
await onDataChange();
|
|
103
|
-
} catch (error: unknown) {
|
|
104
|
-
toast.error(
|
|
105
|
-
error instanceof Error ? error.message : "Failed to delete role"
|
|
106
|
-
);
|
|
107
|
-
}
|
|
120
|
+
deleteRoleMutation.mutate(roleToDelete);
|
|
108
121
|
};
|
|
109
122
|
|
|
110
123
|
return (
|
|
@@ -128,7 +141,7 @@ export const RolesTab: React.FC<RolesTabProps> = ({
|
|
|
128
141
|
<TableHeader>
|
|
129
142
|
<TableRow>
|
|
130
143
|
<TableHead>Role</TableHead>
|
|
131
|
-
<TableHead>
|
|
144
|
+
<TableHead>Access Rules</TableHead>
|
|
132
145
|
<TableHead className="text-right">Actions</TableHead>
|
|
133
146
|
</TableRow>
|
|
134
147
|
</TableHeader>
|
|
@@ -159,7 +172,7 @@ export const RolesTab: React.FC<RolesTabProps> = ({
|
|
|
159
172
|
</TableCell>
|
|
160
173
|
<TableCell>
|
|
161
174
|
<span className="text-sm text-muted-foreground">
|
|
162
|
-
{role.
|
|
175
|
+
{role.accessRules?.length || 0} access rules
|
|
163
176
|
</span>
|
|
164
177
|
</TableCell>
|
|
165
178
|
<TableCell className="text-right">
|
|
@@ -192,7 +205,7 @@ export const RolesTab: React.FC<RolesTabProps> = ({
|
|
|
192
205
|
)
|
|
193
206
|
) : (
|
|
194
207
|
<p className="text-muted-foreground">
|
|
195
|
-
You don't have
|
|
208
|
+
You don't have access to view roles.
|
|
196
209
|
</p>
|
|
197
210
|
)}
|
|
198
211
|
</CardContent>
|
|
@@ -202,7 +215,7 @@ export const RolesTab: React.FC<RolesTabProps> = ({
|
|
|
202
215
|
open={roleDialogOpen}
|
|
203
216
|
onOpenChange={setRoleDialogOpen}
|
|
204
217
|
role={editingRole}
|
|
205
|
-
|
|
218
|
+
accessRulesList={accessRulesList}
|
|
206
219
|
isUserRole={editingRole ? userRoleIds.includes(editingRole.id) : false}
|
|
207
220
|
onSave={handleSaveRole}
|
|
208
221
|
/>
|
|
@@ -15,8 +15,7 @@ import {
|
|
|
15
15
|
useToast,
|
|
16
16
|
} from "@checkstack/ui";
|
|
17
17
|
import { Shield, RefreshCw } from "lucide-react";
|
|
18
|
-
import {
|
|
19
|
-
import { rpcApiRef } from "@checkstack/frontend-api";
|
|
18
|
+
import { usePluginClient } from "@checkstack/frontend-api";
|
|
20
19
|
import { AuthApi } from "@checkstack/auth-common";
|
|
21
20
|
import type { AuthStrategy } from "../api";
|
|
22
21
|
import { AuthStrategyCard } from "./AuthStrategyCard";
|
|
@@ -34,8 +33,7 @@ export const StrategiesTab: React.FC<StrategiesTabProps> = ({
|
|
|
34
33
|
canManageRegistration,
|
|
35
34
|
onDataChange,
|
|
36
35
|
}) => {
|
|
37
|
-
const
|
|
38
|
-
const authClient = rpcApi.forPlugin(AuthApi);
|
|
36
|
+
const authClient = usePluginClient(AuthApi);
|
|
39
37
|
const toast = useToast();
|
|
40
38
|
|
|
41
39
|
const [reloading, setReloading] = useState(false);
|
|
@@ -45,14 +43,9 @@ export const StrategiesTab: React.FC<StrategiesTabProps> = ({
|
|
|
45
43
|
>({});
|
|
46
44
|
|
|
47
45
|
// Registration state
|
|
48
|
-
const [registrationSchema, setRegistrationSchema] = useState<
|
|
49
|
-
Record<string, unknown> | undefined
|
|
50
|
-
>();
|
|
51
46
|
const [registrationSettings, setRegistrationSettings] = useState<{
|
|
52
47
|
allowRegistration: boolean;
|
|
53
48
|
}>({ allowRegistration: true });
|
|
54
|
-
const [loadingRegistration, setLoadingRegistration] = useState(true);
|
|
55
|
-
const [savingRegistration, setSavingRegistration] = useState(false);
|
|
56
49
|
const [registrationValid, setRegistrationValid] = useState(true);
|
|
57
50
|
|
|
58
51
|
// Initialize strategy configs when strategies change
|
|
@@ -64,104 +57,101 @@ export const StrategiesTab: React.FC<StrategiesTabProps> = ({
|
|
|
64
57
|
setStrategyConfigs(configs);
|
|
65
58
|
}, [strategies]);
|
|
66
59
|
|
|
67
|
-
//
|
|
60
|
+
// Query: Registration schema (admin only)
|
|
61
|
+
const { data: registrationSchema, isLoading: schemaLoading } =
|
|
62
|
+
authClient.getRegistrationSchema.useQuery(
|
|
63
|
+
{},
|
|
64
|
+
{ enabled: canManageRegistration }
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
// Query: Registration status (admin only)
|
|
68
|
+
const { data: registrationStatus, isLoading: statusLoading } =
|
|
69
|
+
authClient.getRegistrationStatus.useQuery(
|
|
70
|
+
{},
|
|
71
|
+
{ enabled: canManageRegistration }
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
// Sync fetched settings to local state
|
|
68
75
|
useEffect(() => {
|
|
69
|
-
if (
|
|
70
|
-
|
|
71
|
-
return;
|
|
76
|
+
if (registrationStatus) {
|
|
77
|
+
setRegistrationSettings(registrationStatus);
|
|
72
78
|
}
|
|
79
|
+
}, [registrationStatus]);
|
|
73
80
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
81
|
+
const loadingRegistration = schemaLoading || statusLoading;
|
|
82
|
+
|
|
83
|
+
// Mutations
|
|
84
|
+
const updateStrategyMutation = authClient.updateStrategy.useMutation({
|
|
85
|
+
onSuccess: () => {
|
|
86
|
+
toast.success("Strategy updated");
|
|
87
|
+
void onDataChange();
|
|
88
|
+
},
|
|
89
|
+
onError: (error) => {
|
|
90
|
+
toast.error(
|
|
91
|
+
error instanceof Error ? error.message : "Failed to update strategy"
|
|
92
|
+
);
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
const setRegistrationMutation = authClient.setRegistrationStatus.useMutation({
|
|
97
|
+
onSuccess: () => {
|
|
98
|
+
toast.success("Registration settings saved");
|
|
99
|
+
},
|
|
100
|
+
onError: (error) => {
|
|
101
|
+
toast.error(
|
|
102
|
+
error instanceof Error ? error.message : "Failed to save settings"
|
|
103
|
+
);
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
const reloadAuthMutation = authClient.reloadAuth.useMutation({
|
|
108
|
+
onSuccess: () => {
|
|
109
|
+
toast.success("Authentication system reloaded");
|
|
110
|
+
void onDataChange();
|
|
111
|
+
setReloading(false);
|
|
112
|
+
},
|
|
113
|
+
onError: (error) => {
|
|
114
|
+
toast.error(
|
|
115
|
+
error instanceof Error ? error.message : "Failed to reload auth"
|
|
116
|
+
);
|
|
117
|
+
setReloading(false);
|
|
118
|
+
},
|
|
119
|
+
});
|
|
91
120
|
|
|
92
121
|
const handleToggleStrategy = async (strategyId: string, enabled: boolean) => {
|
|
93
|
-
|
|
94
|
-
await authClient.updateStrategy({ id: strategyId, enabled });
|
|
95
|
-
await onDataChange();
|
|
96
|
-
} catch (error: unknown) {
|
|
97
|
-
const message =
|
|
98
|
-
error instanceof Error ? error.message : "Failed to toggle strategy";
|
|
99
|
-
toast.error(message);
|
|
100
|
-
}
|
|
122
|
+
await updateStrategyMutation.mutateAsync({ id: strategyId, enabled });
|
|
101
123
|
};
|
|
102
124
|
|
|
103
125
|
const handleSaveStrategyConfig = async (
|
|
104
126
|
strategyId: string,
|
|
105
127
|
config: Record<string, unknown>
|
|
106
128
|
) => {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
setStrategyConfigs({
|
|
114
|
-
...strategyConfigs,
|
|
115
|
-
[strategyId]: config,
|
|
116
|
-
});
|
|
117
|
-
await authClient.updateStrategy({
|
|
118
|
-
id: strategyId,
|
|
119
|
-
enabled: strategy.enabled,
|
|
120
|
-
config,
|
|
121
|
-
});
|
|
122
|
-
toast.success(
|
|
123
|
-
"Configuration saved successfully! Click 'Reload Authentication' to apply changes."
|
|
124
|
-
);
|
|
125
|
-
} catch (error: unknown) {
|
|
126
|
-
const message =
|
|
127
|
-
error instanceof Error
|
|
128
|
-
? error.message
|
|
129
|
-
: "Failed to save strategy configuration";
|
|
130
|
-
toast.error(message);
|
|
129
|
+
const strategy = strategies.find((s) => s.id === strategyId);
|
|
130
|
+
if (!strategy) {
|
|
131
|
+
toast.error("Strategy not found");
|
|
132
|
+
return;
|
|
131
133
|
}
|
|
134
|
+
setStrategyConfigs({
|
|
135
|
+
...strategyConfigs,
|
|
136
|
+
[strategyId]: config,
|
|
137
|
+
});
|
|
138
|
+
await updateStrategyMutation.mutateAsync({
|
|
139
|
+
id: strategyId,
|
|
140
|
+
enabled: strategy.enabled,
|
|
141
|
+
config,
|
|
142
|
+
});
|
|
143
|
+
toast.success(
|
|
144
|
+
"Configuration saved successfully! Click 'Reload Authentication' to apply changes."
|
|
145
|
+
);
|
|
132
146
|
};
|
|
133
147
|
|
|
134
|
-
const handleSaveRegistration =
|
|
135
|
-
|
|
136
|
-
try {
|
|
137
|
-
await authClient.setRegistrationStatus(registrationSettings);
|
|
138
|
-
toast.success("Registration settings saved successfully");
|
|
139
|
-
} catch (error: unknown) {
|
|
140
|
-
toast.error(
|
|
141
|
-
error instanceof Error
|
|
142
|
-
? error.message
|
|
143
|
-
: "Failed to save registration settings"
|
|
144
|
-
);
|
|
145
|
-
} finally {
|
|
146
|
-
setSavingRegistration(false);
|
|
147
|
-
}
|
|
148
|
+
const handleSaveRegistration = () => {
|
|
149
|
+
setRegistrationMutation.mutate(registrationSettings);
|
|
148
150
|
};
|
|
149
151
|
|
|
150
|
-
const handleReloadAuth =
|
|
152
|
+
const handleReloadAuth = () => {
|
|
151
153
|
setReloading(true);
|
|
152
|
-
|
|
153
|
-
await authClient.reloadAuth();
|
|
154
|
-
toast.success("Authentication system reloaded successfully");
|
|
155
|
-
await onDataChange();
|
|
156
|
-
} catch (error: unknown) {
|
|
157
|
-
toast.error(
|
|
158
|
-
error instanceof Error
|
|
159
|
-
? error.message
|
|
160
|
-
: "Failed to reload authentication"
|
|
161
|
-
);
|
|
162
|
-
} finally {
|
|
163
|
-
setReloading(false);
|
|
164
|
-
}
|
|
154
|
+
reloadAuthMutation.mutate({});
|
|
165
155
|
};
|
|
166
156
|
|
|
167
157
|
const enabledStrategies = strategies.filter((s) => s.enabled);
|
|
@@ -183,7 +173,7 @@ export const StrategiesTab: React.FC<StrategiesTabProps> = ({
|
|
|
183
173
|
) : registrationSchema ? (
|
|
184
174
|
<div className="space-y-4">
|
|
185
175
|
<DynamicForm
|
|
186
|
-
schema={registrationSchema}
|
|
176
|
+
schema={registrationSchema as Record<string, unknown>}
|
|
187
177
|
value={registrationSettings}
|
|
188
178
|
onChange={(value) =>
|
|
189
179
|
setRegistrationSettings(
|
|
@@ -193,10 +183,14 @@ export const StrategiesTab: React.FC<StrategiesTabProps> = ({
|
|
|
193
183
|
onValidChange={setRegistrationValid}
|
|
194
184
|
/>
|
|
195
185
|
<Button
|
|
196
|
-
onClick={
|
|
197
|
-
disabled={
|
|
186
|
+
onClick={handleSaveRegistration}
|
|
187
|
+
disabled={
|
|
188
|
+
setRegistrationMutation.isPending || !registrationValid
|
|
189
|
+
}
|
|
198
190
|
>
|
|
199
|
-
{
|
|
191
|
+
{setRegistrationMutation.isPending
|
|
192
|
+
? "Saving..."
|
|
193
|
+
: "Save Settings"}
|
|
200
194
|
</Button>
|
|
201
195
|
</div>
|
|
202
196
|
) : (
|
|
@@ -206,7 +200,7 @@ export const StrategiesTab: React.FC<StrategiesTabProps> = ({
|
|
|
206
200
|
)
|
|
207
201
|
) : (
|
|
208
202
|
<p className="text-sm text-muted-foreground">
|
|
209
|
-
You don't have
|
|
203
|
+
You don't have access to manage registration settings.
|
|
210
204
|
</p>
|
|
211
205
|
)}
|
|
212
206
|
</CardContent>
|
|
@@ -268,7 +262,7 @@ export const StrategiesTab: React.FC<StrategiesTabProps> = ({
|
|
|
268
262
|
|
|
269
263
|
{!canManageStrategies && (
|
|
270
264
|
<p className="text-xs text-muted-foreground mt-4">
|
|
271
|
-
You don't have
|
|
265
|
+
You don't have access to manage strategies.
|
|
272
266
|
</p>
|
|
273
267
|
)}
|
|
274
268
|
</div>
|