strapi-plugin-keycloak-realm-users 1.0.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.
Files changed (123) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +485 -0
  3. package/__tests__/constants.test.mjs +207 -0
  4. package/__tests__/mocks/strapi.mjs +182 -0
  5. package/__tests__/services/audit-log.test.mjs +283 -0
  6. package/__tests__/services/keycloak-client.test.mjs +651 -0
  7. package/__tests__/services/permission.test.mjs +374 -0
  8. package/__tests__/services/realm.test.mjs +415 -0
  9. package/__tests__/services/user.test.mjs +487 -0
  10. package/__tests__/utils/errors.test.mjs +109 -0
  11. package/admin/src/components/Initializer.jsx +14 -0
  12. package/admin/src/components/RealmBadge.jsx +17 -0
  13. package/admin/src/constants.js +14 -0
  14. package/admin/src/hooks/useAuditLogs.js +142 -0
  15. package/admin/src/hooks/useKeycloakRoles.js +182 -0
  16. package/admin/src/hooks/useKeycloakUsers.js +477 -0
  17. package/admin/src/hooks/useRealmAdmins.js +249 -0
  18. package/admin/src/hooks/useRealms.js +269 -0
  19. package/admin/src/index.js +46 -0
  20. package/admin/src/pages/App.jsx +21 -0
  21. package/admin/src/pages/AuditPage/index.jsx +213 -0
  22. package/admin/src/pages/RealmsPage/RealmEditPage.jsx +791 -0
  23. package/admin/src/pages/RealmsPage/RealmListPage.jsx +231 -0
  24. package/admin/src/pages/RealmsPage/index.jsx +7 -0
  25. package/admin/src/pages/UsersPage/UserEditPage.jsx +313 -0
  26. package/admin/src/pages/UsersPage/UserListPage.jsx +437 -0
  27. package/admin/src/pages/UsersPage/index.jsx +7 -0
  28. package/admin/src/pluginId.js +2 -0
  29. package/admin/src/translations/en.json +77 -0
  30. package/admin/src/translations/fr.json +77 -0
  31. package/babel.config.cjs +17 -0
  32. package/coverage/clover.xml +422 -0
  33. package/coverage/coverage-final.json +8 -0
  34. package/coverage/lcov-report/base.css +224 -0
  35. package/coverage/lcov-report/block-navigation.js +87 -0
  36. package/coverage/lcov-report/favicon.png +0 -0
  37. package/coverage/lcov-report/index.html +146 -0
  38. package/coverage/lcov-report/prettify.css +1 -0
  39. package/coverage/lcov-report/prettify.js +2 -0
  40. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  41. package/coverage/lcov-report/sorter.js +210 -0
  42. package/coverage/lcov-report/src/bootstrap.js.html +346 -0
  43. package/coverage/lcov-report/src/config/index.html +116 -0
  44. package/coverage/lcov-report/src/config/index.js.html +106 -0
  45. package/coverage/lcov-report/src/constants.js.html +850 -0
  46. package/coverage/lcov-report/src/content-types/audit-log/index.html +116 -0
  47. package/coverage/lcov-report/src/content-types/audit-log/index.js.html +94 -0
  48. package/coverage/lcov-report/src/content-types/index.html +116 -0
  49. package/coverage/lcov-report/src/content-types/index.js.html +112 -0
  50. package/coverage/lcov-report/src/content-types/realm-admin/index.html +116 -0
  51. package/coverage/lcov-report/src/content-types/realm-admin/index.js.html +94 -0
  52. package/coverage/lcov-report/src/content-types/realm-config/index.html +116 -0
  53. package/coverage/lcov-report/src/content-types/realm-config/index.js.html +94 -0
  54. package/coverage/lcov-report/src/controllers/audit.js.html +517 -0
  55. package/coverage/lcov-report/src/controllers/index.html +161 -0
  56. package/coverage/lcov-report/src/controllers/index.js.html +112 -0
  57. package/coverage/lcov-report/src/controllers/realm.js.html +1057 -0
  58. package/coverage/lcov-report/src/controllers/user.js.html +1324 -0
  59. package/coverage/lcov-report/src/destroy.js.html +100 -0
  60. package/coverage/lcov-report/src/index.html +116 -0
  61. package/coverage/lcov-report/src/policies/can-access-realm.js.html +163 -0
  62. package/coverage/lcov-report/src/policies/index.html +146 -0
  63. package/coverage/lcov-report/src/policies/index.js.html +106 -0
  64. package/coverage/lcov-report/src/policies/is-authenticated.js.html +100 -0
  65. package/coverage/lcov-report/src/register.js.html +106 -0
  66. package/coverage/lcov-report/src/routes/admin.js.html +844 -0
  67. package/coverage/lcov-report/src/routes/index.html +131 -0
  68. package/coverage/lcov-report/src/routes/index.js.html +109 -0
  69. package/coverage/lcov-report/src/services/audit-log.js.html +673 -0
  70. package/coverage/lcov-report/src/services/index.html +176 -0
  71. package/coverage/lcov-report/src/services/index.js.html +124 -0
  72. package/coverage/lcov-report/src/services/keycloak-client.js.html +2359 -0
  73. package/coverage/lcov-report/src/services/permission.js.html +955 -0
  74. package/coverage/lcov-report/src/services/realm.js.html +1207 -0
  75. package/coverage/lcov-report/src/services/user.js.html +1924 -0
  76. package/coverage/lcov-report/src/utils/errors.js.html +274 -0
  77. package/coverage/lcov-report/src/utils/index.html +116 -0
  78. package/coverage/lcov-report/src/utils/index.js.html +103 -0
  79. package/coverage/lcov.info +804 -0
  80. package/dist/_chunks/App-BaKrvCeS.mjs +1975 -0
  81. package/dist/_chunks/App-DO6syS77.js +1975 -0
  82. package/dist/_chunks/en-Li-XBDe9.mjs +72 -0
  83. package/dist/_chunks/en-aCyfgNfr.js +72 -0
  84. package/dist/_chunks/fr-Cj33Q8jI.js +72 -0
  85. package/dist/_chunks/fr-vLrXph-Z.mjs +72 -0
  86. package/dist/_chunks/index-DwDO4-0C.js +69 -0
  87. package/dist/_chunks/index-jTVd7LdQ.mjs +70 -0
  88. package/dist/admin/index.js +3 -0
  89. package/dist/admin/index.mjs +4 -0
  90. package/dist/server/index.js +3003 -0
  91. package/dist/server/index.mjs +3004 -0
  92. package/jest.config.cjs +50 -0
  93. package/package.json +55 -0
  94. package/server/src/bootstrap.js +87 -0
  95. package/server/src/config/index.js +7 -0
  96. package/server/src/constants.js +255 -0
  97. package/server/src/content-types/audit-log/index.js +3 -0
  98. package/server/src/content-types/audit-log/schema.json +61 -0
  99. package/server/src/content-types/index.js +9 -0
  100. package/server/src/content-types/realm-admin/index.js +3 -0
  101. package/server/src/content-types/realm-admin/schema.json +45 -0
  102. package/server/src/content-types/realm-config/index.js +3 -0
  103. package/server/src/content-types/realm-config/schema.json +56 -0
  104. package/server/src/controllers/audit.js +144 -0
  105. package/server/src/controllers/index.js +9 -0
  106. package/server/src/controllers/realm.js +324 -0
  107. package/server/src/controllers/user.js +413 -0
  108. package/server/src/destroy.js +5 -0
  109. package/server/src/index.js +21 -0
  110. package/server/src/policies/can-access-realm.js +26 -0
  111. package/server/src/policies/index.js +7 -0
  112. package/server/src/policies/is-authenticated.js +5 -0
  113. package/server/src/register.js +7 -0
  114. package/server/src/routes/admin.js +253 -0
  115. package/server/src/routes/index.js +8 -0
  116. package/server/src/services/audit-log.js +196 -0
  117. package/server/src/services/index.js +13 -0
  118. package/server/src/services/keycloak-client.js +758 -0
  119. package/server/src/services/permission.js +290 -0
  120. package/server/src/services/realm.js +374 -0
  121. package/server/src/services/user.js +613 -0
  122. package/server/src/utils/errors.js +63 -0
  123. package/server/src/utils/index.js +6 -0
@@ -0,0 +1,213 @@
1
+ import { useState } from 'react';
2
+ import { useNavigate } from 'react-router-dom';
3
+ import { useIntl } from 'react-intl';
4
+ import {
5
+ Button,
6
+ Table,
7
+ Thead,
8
+ Tbody,
9
+ Tr,
10
+ Th,
11
+ Td,
12
+ Typography,
13
+ Badge,
14
+ Flex,
15
+ Box,
16
+ EmptyStateLayout,
17
+ Loader,
18
+ SingleSelect,
19
+ SingleSelectOption,
20
+ Field,
21
+ } from '@strapi/design-system';
22
+ import { Layouts } from '@strapi/strapi/admin';
23
+ import { ArrowLeft, File } from '@strapi/icons';
24
+
25
+ import { getTrad } from '../../constants';
26
+ import useAuditLogs from '../../hooks/useAuditLogs';
27
+ import useRealms from '../../hooks/useRealms';
28
+ import pluginId from '../../pluginId';
29
+
30
+ const ACTION_COLORS = {
31
+ CREATE_USER: 'success100',
32
+ UPDATE_USER: 'primary100',
33
+ DELETE_USER: 'danger100',
34
+ RESET_PASSWORD: 'warning100',
35
+ ASSIGN_ROLE: 'success100',
36
+ REMOVE_ROLE: 'warning100',
37
+ ENABLE_USER: 'success100',
38
+ DISABLE_USER: 'warning100',
39
+ SEND_VERIFY_EMAIL: 'primary100',
40
+ SEND_RESET_PASSWORD_EMAIL: 'primary100',
41
+ BULK_IMPORT: 'success100',
42
+ };
43
+
44
+ const formatDate = (dateString) => {
45
+ const date = new Date(dateString);
46
+ return date.toLocaleString();
47
+ };
48
+
49
+ const AuditPage = () => {
50
+ const navigate = useNavigate();
51
+ const { formatMessage } = useIntl();
52
+ const { realms } = useRealms();
53
+ const [selectedRealm, setSelectedRealm] = useState('');
54
+ const [selectedAction, setSelectedAction] = useState('');
55
+
56
+ const { logs, isLoading, fetchLogs } = useAuditLogs();
57
+
58
+ const handleRealmChange = (value) => {
59
+ setSelectedRealm(value);
60
+ fetchLogs({ realmName: value || undefined, action: selectedAction || undefined });
61
+ };
62
+
63
+ const handleActionChange = (value) => {
64
+ setSelectedAction(value);
65
+ fetchLogs({ realmName: selectedRealm || undefined, action: value || undefined });
66
+ };
67
+
68
+ return (
69
+ <Layouts.Root>
70
+ <Layouts.Header
71
+ title={formatMessage({ id: getTrad('audit.title'), defaultMessage: 'Audit Log' })}
72
+ subtitle={formatMessage({
73
+ id: getTrad('audit.subtitle'),
74
+ defaultMessage: 'Track user management actions',
75
+ })}
76
+ navigationAction={
77
+ <Button
78
+ startIcon={<ArrowLeft />}
79
+ variant="ghost"
80
+ onClick={() => navigate(`/settings/${pluginId}`)}
81
+ >
82
+ {formatMessage({ id: getTrad('common.back'), defaultMessage: 'Back' })}
83
+ </Button>
84
+ }
85
+ />
86
+
87
+ <Layouts.Content>
88
+ <Box marginBottom={4}>
89
+ <Flex gap={4}>
90
+ <Box width="200px">
91
+ <Field.Root>
92
+ <Field.Label>Filter by Realm</Field.Label>
93
+ <SingleSelect
94
+ value={selectedRealm}
95
+ onChange={handleRealmChange}
96
+ placeholder="All realms"
97
+ >
98
+ <SingleSelectOption value="">All realms</SingleSelectOption>
99
+ {realms.map((realm) => (
100
+ <SingleSelectOption key={realm.documentId} value={realm.name}>
101
+ {realm.displayName}
102
+ </SingleSelectOption>
103
+ ))}
104
+ </SingleSelect>
105
+ </Field.Root>
106
+ </Box>
107
+ <Box width="200px">
108
+ <Field.Root>
109
+ <Field.Label>Filter by Action</Field.Label>
110
+ <SingleSelect
111
+ value={selectedAction}
112
+ onChange={handleActionChange}
113
+ placeholder="All actions"
114
+ >
115
+ <SingleSelectOption value="">All actions</SingleSelectOption>
116
+ <SingleSelectOption value="CREATE_USER">Create User</SingleSelectOption>
117
+ <SingleSelectOption value="UPDATE_USER">Update User</SingleSelectOption>
118
+ <SingleSelectOption value="DELETE_USER">Delete User</SingleSelectOption>
119
+ <SingleSelectOption value="RESET_PASSWORD">Reset Password</SingleSelectOption>
120
+ <SingleSelectOption value="ASSIGN_ROLE">Assign Role</SingleSelectOption>
121
+ <SingleSelectOption value="REMOVE_ROLE">Remove Role</SingleSelectOption>
122
+ <SingleSelectOption value="ENABLE_USER">Enable User</SingleSelectOption>
123
+ <SingleSelectOption value="DISABLE_USER">Disable User</SingleSelectOption>
124
+ <SingleSelectOption value="BULK_IMPORT">Bulk Import</SingleSelectOption>
125
+ </SingleSelect>
126
+ </Field.Root>
127
+ </Box>
128
+ </Flex>
129
+ </Box>
130
+
131
+ {isLoading ? (
132
+ <Flex justifyContent="center" padding={8}>
133
+ <Loader />
134
+ </Flex>
135
+ ) : logs.length === 0 ? (
136
+ <EmptyStateLayout
137
+ icon={<File width="10rem" height="10rem" />}
138
+ content="No audit logs found"
139
+ />
140
+ ) : (
141
+ <Table colCount={6} rowCount={logs.length + 1}>
142
+ <Thead>
143
+ <Tr>
144
+ <Th>
145
+ <Typography variant="sigma">
146
+ {formatMessage({ id: getTrad('audit.date'), defaultMessage: 'Date' })}
147
+ </Typography>
148
+ </Th>
149
+ <Th>
150
+ <Typography variant="sigma">
151
+ {formatMessage({ id: getTrad('audit.action'), defaultMessage: 'Action' })}
152
+ </Typography>
153
+ </Th>
154
+ <Th>
155
+ <Typography variant="sigma">
156
+ {formatMessage({ id: getTrad('audit.realm'), defaultMessage: 'Realm' })}
157
+ </Typography>
158
+ </Th>
159
+ <Th>
160
+ <Typography variant="sigma">
161
+ {formatMessage({ id: getTrad('audit.user'), defaultMessage: 'Keycloak User' })}
162
+ </Typography>
163
+ </Th>
164
+ <Th>
165
+ <Typography variant="sigma">
166
+ {formatMessage({ id: getTrad('audit.performedBy'), defaultMessage: 'Performed By' })}
167
+ </Typography>
168
+ </Th>
169
+ <Th>
170
+ <Typography variant="sigma">Details</Typography>
171
+ </Th>
172
+ </Tr>
173
+ </Thead>
174
+ <Tbody>
175
+ {logs.map((log) => (
176
+ <Tr key={log.documentId}>
177
+ <Td>
178
+ <Typography textColor="neutral600">{formatDate(log.createdAt)}</Typography>
179
+ </Td>
180
+ <Td>
181
+ <Badge backgroundColor={ACTION_COLORS[log.action] || 'neutral150'}>
182
+ {log.action.replace(/_/g, ' ')}
183
+ </Badge>
184
+ </Td>
185
+ <Td>
186
+ <Typography textColor="neutral800">
187
+ {log.realmDisplayName || log.realmName}
188
+ </Typography>
189
+ </Td>
190
+ <Td>
191
+ <Typography textColor="neutral800">
192
+ {log.keycloakUsername || log.keycloakUserId || '-'}
193
+ </Typography>
194
+ </Td>
195
+ <Td>
196
+ <Typography textColor="neutral600">{log.performedByEmail || '-'}</Typography>
197
+ </Td>
198
+ <Td>
199
+ <Typography textColor="neutral500" ellipsis>
200
+ {log.details ? JSON.stringify(log.details).substring(0, 50) : '-'}
201
+ </Typography>
202
+ </Td>
203
+ </Tr>
204
+ ))}
205
+ </Tbody>
206
+ </Table>
207
+ )}
208
+ </Layouts.Content>
209
+ </Layouts.Root>
210
+ );
211
+ };
212
+
213
+ export default AuditPage;