strapi-plugin-oidc 1.5.3 → 1.6.1

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.
@@ -2,10 +2,10 @@ import { jsxs, Fragment, jsx } from "react/jsx-runtime";
2
2
  import { useBlocker, Routes, Route } from "react-router-dom";
3
3
  import { useNotification, useFetchClient, Page, Layouts } from "@strapi/strapi/admin";
4
4
  import { useState, useRef, useCallback, useEffect, memo } from "react";
5
- import { Typography, Flex, Box, MultiSelect, MultiSelectOption, Button, Dialog, Field, Divider, Thead, Tr, Th, Tbody, Td, IconButton, Pagination, PreviousLink, PageLink, NextLink, Table, Alert } from "@strapi/design-system";
6
- import { Download, Upload, Trash, WarningCircle, Plus, Information } from "@strapi/icons";
5
+ import { Typography, Flex, Box, MultiSelect, MultiSelectOption, Dialog, Button, Table, Pagination, PreviousLink, NextLink, PageLink, Field, Divider, Thead, Tr, Th, Tbody, Td, IconButton, Tooltip, Alert } from "@strapi/design-system";
6
+ import { WarningCircle, Download, Upload, Trash, Plus, Information } from "@strapi/icons";
7
7
  import { useIntl } from "react-intl";
8
- import { g as getTrad } from "./index-Dzf0bJC1.mjs";
8
+ import { g as getTrad } from "./index-P9HriRms.mjs";
9
9
  import styled from "styled-components";
10
10
  function Role({ oidcRoles, roles, onChangeRole }) {
11
11
  const { formatMessage } = useIntl();
@@ -27,6 +27,20 @@ function Role({ oidcRoles, roles, onChangeRole }) {
27
27
  ) }, oidcRole.oauth_type)) })
28
28
  ] });
29
29
  }
30
+ function LocalizedDate({
31
+ date,
32
+ options
33
+ }) {
34
+ const userLocale = navigator.language || "en-US";
35
+ return new Intl.DateTimeFormat(userLocale, {
36
+ year: "numeric",
37
+ month: "short",
38
+ day: "numeric",
39
+ hour: "2-digit",
40
+ minute: "2-digit",
41
+ ...options
42
+ }).format(new Date(date));
43
+ }
30
44
  const CustomTable = styled(Table)`
31
45
  th,
32
46
  td,
@@ -35,20 +49,96 @@ const CustomTable = styled(Table)`
35
49
  font-size: 1.3rem !important;
36
50
  }
37
51
  `;
38
- function LocalizedDate({ date }) {
39
- const userLocale = navigator.language || "en-US";
40
- return new Intl.DateTimeFormat(userLocale, {
41
- year: "numeric",
42
- month: "long",
43
- day: "numeric",
44
- hour: "2-digit",
45
- minute: "2-digit"
46
- }).format(new Date(date));
52
+ function ConfirmDialog({
53
+ trigger,
54
+ title,
55
+ body,
56
+ confirmLabel,
57
+ onConfirm,
58
+ confirmVariant = "danger"
59
+ }) {
60
+ const { formatMessage } = useIntl();
61
+ return /* @__PURE__ */ jsxs(Dialog.Root, { children: [
62
+ /* @__PURE__ */ jsx(Dialog.Trigger, { children: trigger }),
63
+ /* @__PURE__ */ jsxs(Dialog.Content, { children: [
64
+ /* @__PURE__ */ jsx(Dialog.Header, { children: title }),
65
+ /* @__PURE__ */ jsx(Dialog.Body, { icon: /* @__PURE__ */ jsx(WarningCircle, { fill: "danger600" }), children: body }),
66
+ /* @__PURE__ */ jsxs(Dialog.Footer, { children: [
67
+ /* @__PURE__ */ jsx(Dialog.Cancel, { children: /* @__PURE__ */ jsx(Button, { fullWidth: true, variant: "tertiary", children: formatMessage(getTrad("page.cancel")) }) }),
68
+ /* @__PURE__ */ jsx(Dialog.Action, { children: /* @__PURE__ */ jsx(Button, { fullWidth: true, variant: confirmVariant, onClick: onConfirm, children: confirmLabel }) })
69
+ ] })
70
+ ] })
71
+ ] });
72
+ }
73
+ function TablePagination({ page, pageCount, onPageChange }) {
74
+ const { formatMessage } = useIntl();
75
+ if (pageCount <= 1) return null;
76
+ const pageLink = (num) => /* @__PURE__ */ jsx(
77
+ PageLink,
78
+ {
79
+ number: num,
80
+ href: "#",
81
+ onClick: (e) => {
82
+ e.preventDefault();
83
+ onPageChange(num);
84
+ },
85
+ children: formatMessage(getTrad("pagination.page"), { page: num })
86
+ },
87
+ num
88
+ );
89
+ const Ellipsis = () => /* @__PURE__ */ jsx(Typography, { textColor: "neutral600", paddingLeft: 2, paddingRight: 2, children: "…" });
90
+ let pages;
91
+ if (pageCount <= 10) {
92
+ pages = Array.from({ length: pageCount }, (_, i) => pageLink(i + 1));
93
+ } else if (page <= 6) {
94
+ pages = /* @__PURE__ */ jsxs(Fragment, { children: [
95
+ Array.from({ length: 9 }, (_, i) => pageLink(i + 1)),
96
+ /* @__PURE__ */ jsx(Ellipsis, {}),
97
+ pageLink(pageCount)
98
+ ] });
99
+ } else if (page >= pageCount - 5) {
100
+ pages = /* @__PURE__ */ jsxs(Fragment, { children: [
101
+ pageLink(1),
102
+ /* @__PURE__ */ jsx(Ellipsis, {}),
103
+ Array.from({ length: 9 }, (_, i) => pageLink(pageCount - 8 + i))
104
+ ] });
105
+ } else {
106
+ pages = /* @__PURE__ */ jsxs(Fragment, { children: [
107
+ pageLink(1),
108
+ /* @__PURE__ */ jsx(Ellipsis, {}),
109
+ Array.from({ length: 7 }, (_, i) => pageLink(page - 3 + i)),
110
+ /* @__PURE__ */ jsx(Ellipsis, {}),
111
+ pageLink(pageCount)
112
+ ] });
113
+ }
114
+ return /* @__PURE__ */ jsx(Box, { paddingTop: 4, children: /* @__PURE__ */ jsx(Flex, { justifyContent: "flex-end", children: /* @__PURE__ */ jsxs(Pagination, { activePage: page, pageCount, children: [
115
+ /* @__PURE__ */ jsx(
116
+ PreviousLink,
117
+ {
118
+ href: "#",
119
+ onClick: (e) => {
120
+ e.preventDefault();
121
+ onPageChange(Math.max(1, page - 1));
122
+ },
123
+ children: formatMessage(getTrad("pagination.previous"))
124
+ }
125
+ ),
126
+ pages,
127
+ /* @__PURE__ */ jsx(
128
+ NextLink,
129
+ {
130
+ href: "#",
131
+ onClick: (e) => {
132
+ e.preventDefault();
133
+ onPageChange(Math.min(pageCount, page + 1));
134
+ },
135
+ children: formatMessage(getTrad("pagination.next"))
136
+ }
137
+ )
138
+ ] }) }) });
47
139
  }
48
140
  function Whitelist({
49
141
  users,
50
- roles,
51
- oidcRoles = [],
52
142
  useWhitelist,
53
143
  loading,
54
144
  onSave,
@@ -58,19 +148,13 @@ function Whitelist({
58
148
  onExport
59
149
  }) {
60
150
  const [email, setEmail] = useState("");
61
- const [selectedRoles, setSelectedRoles] = useState([]);
62
151
  const [page, setPage] = useState(1);
63
152
  const { formatMessage } = useIntl();
64
153
  const { toggleNotification } = useNotification();
65
154
  const fileInputRef = useRef(null);
66
- const PAGE_SIZE = 10;
67
- const pageCount = Math.ceil(users.length / PAGE_SIZE) || 1;
68
- const paginatedUsers = users.slice((page - 1) * PAGE_SIZE, page * PAGE_SIZE);
69
- const getRoleNames = (roleIds) => roleIds.map((roleId) => {
70
- const r = roles.find((ro) => String(ro.id) === String(roleId));
71
- return r ? r.name : roleId;
72
- }).join(", ");
73
- const defaultRoleNames = getRoleNames(oidcRoles.flatMap((oidc) => oidc.role ?? []));
155
+ const PAGE_SIZE2 = 10;
156
+ const pageCount = Math.ceil(users.length / PAGE_SIZE2) || 1;
157
+ const paginatedUsers = users.slice((page - 1) * PAGE_SIZE2, page * PAGE_SIZE2);
74
158
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
75
159
  const isValidEmail = emailRegex.test(email);
76
160
  const onSaveEmail = useCallback(() => {
@@ -81,11 +165,10 @@ function Whitelist({
81
165
  message: formatMessage(getTrad("whitelist.error.unique"))
82
166
  });
83
167
  } else {
84
- onSave(emailText, selectedRoles);
168
+ onSave(emailText);
85
169
  setEmail("");
86
- setSelectedRoles([]);
87
170
  }
88
- }, [email, selectedRoles, users, onSave, formatMessage, toggleNotification]);
171
+ }, [email, users, onSave, formatMessage, toggleNotification]);
89
172
  const handleImport = useCallback(
90
173
  async (e) => {
91
174
  const file = e.target.files?.[0];
@@ -96,11 +179,8 @@ function Whitelist({
96
179
  const text = await file.text();
97
180
  const parsed = JSON.parse(text);
98
181
  if (!Array.isArray(parsed)) throw new Error();
99
- const entries = parsed.filter((item) => item?.email).map((item) => ({
100
- email: String(item.email),
101
- roles: Array.isArray(item.roles) ? item.roles : []
102
- }));
103
- const count = await onImport(entries);
182
+ const emails = parsed.filter((item) => item?.email).map((item) => String(item.email).trim().toLowerCase()).filter((email2) => emailRegex.test(email2));
183
+ const count = await onImport(emails);
104
184
  if (count === 0) {
105
185
  toggleNotification({
106
186
  type: "info",
@@ -158,19 +238,18 @@ function Whitelist({
158
238
  onChange: handleImport
159
239
  }
160
240
  ),
161
- users.length > 0 && /* @__PURE__ */ jsxs(Dialog.Root, { children: [
162
- /* @__PURE__ */ jsx(Dialog.Trigger, { children: /* @__PURE__ */ jsx(Button, { size: "S", variant: "danger-light", startIcon: /* @__PURE__ */ jsx(Trash, {}), children: formatMessage(getTrad("whitelist.delete.all.label")) }) }),
163
- /* @__PURE__ */ jsxs(Dialog.Content, { children: [
164
- /* @__PURE__ */ jsx(Dialog.Header, { children: formatMessage(getTrad("whitelist.delete.all.title")) }),
165
- /* @__PURE__ */ jsx(Dialog.Body, { icon: /* @__PURE__ */ jsx(WarningCircle, { fill: "danger600" }), children: /* @__PURE__ */ jsx(Flex, { justifyContent: "center", children: /* @__PURE__ */ jsx(Typography, { textColor: "neutral800", textAlign: "center", children: formatMessage(getTrad("whitelist.delete.all.description"), {
241
+ users.length > 0 && /* @__PURE__ */ jsx(
242
+ ConfirmDialog,
243
+ {
244
+ trigger: /* @__PURE__ */ jsx(Button, { size: "S", variant: "danger-light", startIcon: /* @__PURE__ */ jsx(Trash, {}), children: formatMessage(getTrad("whitelist.delete.all.label")) }),
245
+ title: formatMessage(getTrad("whitelist.delete.all.title")),
246
+ body: /* @__PURE__ */ jsx(Flex, { justifyContent: "center", children: /* @__PURE__ */ jsx(Typography, { textColor: "neutral800", textAlign: "center", children: formatMessage(getTrad("whitelist.delete.all.description"), {
166
247
  count: users.length
167
- }) }) }) }),
168
- /* @__PURE__ */ jsxs(Dialog.Footer, { children: [
169
- /* @__PURE__ */ jsx(Dialog.Cancel, { children: /* @__PURE__ */ jsx(Button, { fullWidth: true, variant: "tertiary", children: formatMessage(getTrad("page.cancel")) }) }),
170
- /* @__PURE__ */ jsx(Dialog.Action, { children: /* @__PURE__ */ jsx(Button, { fullWidth: true, variant: "danger", onClick: onDeleteAll, children: formatMessage(getTrad("whitelist.delete.all.label")) }) })
171
- ] })
172
- ] })
173
- ] })
248
+ }) }) }),
249
+ confirmLabel: formatMessage(getTrad("whitelist.delete.all.label")),
250
+ onConfirm: onDeleteAll
251
+ }
252
+ )
174
253
  ] })
175
254
  ] }),
176
255
  /* @__PURE__ */ jsxs(Flex, { gap: 4, marginTop: 5, marginBottom: 5, alignItems: "flex-start", children: [
@@ -185,18 +264,6 @@ function Whitelist({
185
264
  placeholder: formatMessage(getTrad("whitelist.email.placeholder"))
186
265
  }
187
266
  ) }) }),
188
- /* @__PURE__ */ jsx(Box, { style: { flex: 1 }, children: /* @__PURE__ */ jsx(Field.Root, { children: /* @__PURE__ */ jsx(
189
- MultiSelect,
190
- {
191
- withTags: true,
192
- placeholder: formatMessage(getTrad("whitelist.roles.placeholder")),
193
- value: selectedRoles,
194
- onChange: (value) => {
195
- setSelectedRoles(value || []);
196
- },
197
- children: roles.map((role) => /* @__PURE__ */ jsx(MultiSelectOption, { value: role.id.toString(), children: role.name }, role.id))
198
- }
199
- ) }) }),
200
267
  /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(
201
268
  Button,
202
269
  {
@@ -210,105 +277,216 @@ function Whitelist({
210
277
  ) })
211
278
  ] }),
212
279
  /* @__PURE__ */ jsx(Divider, {}),
213
- /* @__PURE__ */ jsxs(CustomTable, { colCount: 5, rowCount: users.length, children: [
280
+ /* @__PURE__ */ jsxs(CustomTable, { colCount: 4, rowCount: users.length, children: [
214
281
  /* @__PURE__ */ jsx(Thead, { children: /* @__PURE__ */ jsxs(Tr, { children: [
215
282
  /* @__PURE__ */ jsx(Th, { children: formatMessage(getTrad("whitelist.table.no")) }),
216
283
  /* @__PURE__ */ jsx(Th, { children: formatMessage(getTrad("whitelist.table.email")) }),
217
- /* @__PURE__ */ jsx(Th, { children: formatMessage(getTrad("whitelist.table.roles")) }),
218
284
  /* @__PURE__ */ jsx(Th, { children: formatMessage(getTrad("whitelist.table.created")) }),
219
285
  /* @__PURE__ */ jsx(Th, { style: { paddingRight: 0 }, children: " " })
220
286
  ] }) }),
221
- /* @__PURE__ */ jsx(Tbody, { children: users.length === 0 ? /* @__PURE__ */ jsx(Tr, { children: /* @__PURE__ */ jsx(Td, { colSpan: 5, children: /* @__PURE__ */ jsx(Flex, { justifyContent: "center", padding: 4, children: /* @__PURE__ */ jsx(Typography, { textColor: "neutral600", children: formatMessage(getTrad("whitelist.table.empty")) }) }) }) }) : paginatedUsers.map((user, index) => {
222
- const explicitRoleNames = getRoleNames(user.roles || []);
223
- const isDefault = !explicitRoleNames && Boolean(defaultRoleNames);
224
- const userRolesNames = explicitRoleNames || defaultRoleNames;
225
- return /* @__PURE__ */ jsxs(Tr, { children: [
226
- /* @__PURE__ */ jsx(Td, { children: index + 1 + (page - 1) * PAGE_SIZE }),
227
- /* @__PURE__ */ jsx(Td, { children: user.email }),
228
- /* @__PURE__ */ jsx(Td, { children: userRolesNames ? /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "center", children: [
229
- /* @__PURE__ */ jsx("span", { children: userRolesNames }),
230
- isDefault && /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral500", children: formatMessage(getTrad("whitelist.table.roles.default")) })
231
- ] }) : "-" }),
232
- /* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsx(LocalizedDate, { date: user.createdAt }) }),
233
- /* @__PURE__ */ jsx(Td, { style: { paddingRight: 0 }, children: /* @__PURE__ */ jsx(
234
- Flex,
235
- {
236
- justifyContent: "flex-end",
237
- onClick: (e) => e.stopPropagation(),
238
- style: { width: "100%" },
239
- children: /* @__PURE__ */ jsxs(Dialog.Root, { children: [
240
- /* @__PURE__ */ jsx(Dialog.Trigger, { children: /* @__PURE__ */ jsx(
287
+ /* @__PURE__ */ jsx(Tbody, { children: users.length === 0 ? /* @__PURE__ */ jsx(Tr, { children: /* @__PURE__ */ jsx(Td, { colSpan: 4, children: /* @__PURE__ */ jsx(Flex, { justifyContent: "center", padding: 4, children: /* @__PURE__ */ jsx(Typography, { textColor: "neutral600", children: formatMessage(getTrad("whitelist.table.empty")) }) }) }) }) : paginatedUsers.map((user, index) => /* @__PURE__ */ jsxs(Tr, { children: [
288
+ /* @__PURE__ */ jsx(Td, { children: index + 1 + (page - 1) * PAGE_SIZE2 }),
289
+ /* @__PURE__ */ jsx(Td, { children: user.email }),
290
+ /* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsx(LocalizedDate, { date: user.createdAt, options: { month: "long" } }) }),
291
+ /* @__PURE__ */ jsx(Td, { style: { paddingRight: 0 }, children: /* @__PURE__ */ jsx(
292
+ Flex,
293
+ {
294
+ justifyContent: "flex-end",
295
+ onClick: (e) => e.stopPropagation(),
296
+ style: { width: "100%" },
297
+ children: /* @__PURE__ */ jsx(
298
+ ConfirmDialog,
299
+ {
300
+ trigger: /* @__PURE__ */ jsx(
241
301
  IconButton,
242
302
  {
243
303
  label: formatMessage(getTrad("whitelist.delete.label")),
244
304
  withTooltip: false,
245
305
  children: /* @__PURE__ */ jsx(Trash, {})
246
306
  }
247
- ) }),
248
- /* @__PURE__ */ jsxs(Dialog.Content, { children: [
249
- /* @__PURE__ */ jsx(Dialog.Header, { children: formatMessage(getTrad("whitelist.delete.title")) }),
250
- /* @__PURE__ */ jsx(Dialog.Body, { icon: /* @__PURE__ */ jsx(WarningCircle, { fill: "danger600" }), children: /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "center", gap: 2, children: [
251
- /* @__PURE__ */ jsx(Flex, { justifyContent: "center", children: /* @__PURE__ */ jsx(Typography, { id: "confirm-description", children: formatMessage(getTrad("whitelist.delete.description")) }) }),
252
- /* @__PURE__ */ jsx(Flex, { justifyContent: "center", children: /* @__PURE__ */ jsx(Typography, { variant: "omega", fontWeight: "bold", children: user.email }) }),
253
- /* @__PURE__ */ jsx(Flex, { justifyContent: "center", marginTop: 2, children: /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", children: formatMessage(getTrad("whitelist.delete.note")) }) })
254
- ] }) }),
255
- /* @__PURE__ */ jsxs(Dialog.Footer, { children: [
256
- /* @__PURE__ */ jsx(Dialog.Cancel, { children: /* @__PURE__ */ jsx(Button, { fullWidth: true, variant: "tertiary", children: formatMessage(getTrad("page.cancel")) }) }),
257
- /* @__PURE__ */ jsx(Dialog.Action, { children: /* @__PURE__ */ jsx(
258
- Button,
259
- {
260
- fullWidth: true,
261
- variant: "danger-light",
262
- onClick: () => onDelete(user.email),
263
- children: formatMessage(getTrad("page.ok"))
264
- }
265
- ) })
266
- ] })
267
- ] })
268
- ] })
269
- }
270
- ) })
271
- ] }, user.email);
272
- }) })
307
+ ),
308
+ title: formatMessage(getTrad("whitelist.delete.title")),
309
+ body: /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "center", gap: 2, children: [
310
+ /* @__PURE__ */ jsx(Flex, { justifyContent: "center", children: /* @__PURE__ */ jsx(Typography, { id: "confirm-description", children: formatMessage(getTrad("whitelist.delete.description")) }) }),
311
+ /* @__PURE__ */ jsx(Flex, { justifyContent: "center", children: /* @__PURE__ */ jsx(Typography, { variant: "omega", fontWeight: "bold", children: user.email }) }),
312
+ /* @__PURE__ */ jsx(Flex, { justifyContent: "center", marginTop: 2, children: /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", children: formatMessage(getTrad("whitelist.delete.note")) }) })
313
+ ] }),
314
+ confirmLabel: formatMessage(getTrad("page.ok")),
315
+ onConfirm: () => onDelete(user.email),
316
+ confirmVariant: "danger-light"
317
+ }
318
+ )
319
+ }
320
+ ) })
321
+ ] }, user.email)) })
322
+ ] }),
323
+ /* @__PURE__ */ jsx(TablePagination, { page, pageCount, onPageChange: setPage })
324
+ ] })
325
+ ] });
326
+ }
327
+ const PAGE_SIZE = 10;
328
+ const DETAILS_TEXT_STYLE = {
329
+ display: "block",
330
+ overflow: "hidden",
331
+ textOverflow: "ellipsis",
332
+ whiteSpace: "nowrap",
333
+ maxWidth: "180px",
334
+ cursor: "help"
335
+ };
336
+ function AuditLog() {
337
+ const { formatMessage } = useIntl();
338
+ const { get, del } = useFetchClient();
339
+ const { toggleNotification } = useNotification();
340
+ const [records, setRecords] = useState([]);
341
+ const [pagination, setPagination] = useState({
342
+ page: 1,
343
+ pageSize: PAGE_SIZE,
344
+ total: 0,
345
+ pageCount: 1
346
+ });
347
+ const [page, setPage] = useState(1);
348
+ const [loading, setLoading] = useState(false);
349
+ const fetchLogs = useCallback(
350
+ async (p) => {
351
+ setLoading(true);
352
+ try {
353
+ const response = await get(
354
+ `/strapi-plugin-oidc/audit-logs?page=${p}&pageSize=${PAGE_SIZE}`
355
+ );
356
+ setRecords(response.data.results ?? []);
357
+ setPagination(
358
+ response.data.pagination ?? { page: p, pageSize: PAGE_SIZE, total: 0, pageCount: 1 }
359
+ );
360
+ } catch {
361
+ setRecords([]);
362
+ } finally {
363
+ setLoading(false);
364
+ }
365
+ },
366
+ [get]
367
+ );
368
+ useEffect(() => {
369
+ fetchLogs(page);
370
+ }, [fetchLogs, page]);
371
+ const handleClearAll = async () => {
372
+ try {
373
+ await del("/strapi-plugin-oidc/audit-logs");
374
+ toggleNotification({
375
+ type: "success",
376
+ message: formatMessage(getTrad("auditlog.clear.success"))
377
+ });
378
+ fetchLogs(1);
379
+ } catch {
380
+ toggleNotification({
381
+ type: "danger",
382
+ message: formatMessage(getTrad("auditlog.clear.error"))
383
+ });
384
+ }
385
+ };
386
+ const handleExport = async () => {
387
+ try {
388
+ const cookieMatch = document.cookie.match(/(?:^|;\s*)jwtToken=([^;]+)/);
389
+ const token = cookieMatch ? decodeURIComponent(cookieMatch[1]) : "";
390
+ const response = await fetch("/strapi-plugin-oidc/audit-logs/export", {
391
+ headers: { Authorization: `Bearer ${token}` }
392
+ });
393
+ if (!response.ok) {
394
+ toggleNotification({
395
+ type: "danger",
396
+ message: formatMessage(getTrad("auditlog.export.error"))
397
+ });
398
+ return;
399
+ }
400
+ const text = await response.text();
401
+ const blob = new Blob([text], { type: "application/x-ndjson" });
402
+ const url = URL.createObjectURL(blob);
403
+ const a = document.createElement("a");
404
+ a.href = url;
405
+ a.download = `oidc-audit-log-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}.ndjson`;
406
+ a.click();
407
+ URL.revokeObjectURL(url);
408
+ } catch {
409
+ toggleNotification({
410
+ type: "danger",
411
+ message: formatMessage(getTrad("auditlog.export.error"))
412
+ });
413
+ }
414
+ };
415
+ return /* @__PURE__ */ jsxs(Box, { children: [
416
+ /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", marginBottom: 4, children: [
417
+ /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "neutral600", children: [
418
+ pagination.total,
419
+ " ",
420
+ pagination.total === 1 ? "entry" : "entries"
273
421
  ] }),
274
- pageCount > 1 && /* @__PURE__ */ jsx(Box, { paddingTop: 4, children: /* @__PURE__ */ jsx(Flex, { justifyContent: "flex-end", children: /* @__PURE__ */ jsxs(Pagination, { activePage: page, pageCount, children: [
422
+ /* @__PURE__ */ jsxs(Flex, { gap: 2, children: [
275
423
  /* @__PURE__ */ jsx(
276
- PreviousLink,
424
+ Button,
277
425
  {
278
- href: "#",
279
- onClick: (e) => {
280
- e.preventDefault();
281
- setPage((p) => Math.max(1, p - 1));
282
- },
283
- children: formatMessage(getTrad("pagination.previous"))
426
+ size: "S",
427
+ variant: "tertiary",
428
+ startIcon: /* @__PURE__ */ jsx(Download, {}),
429
+ onClick: handleExport,
430
+ disabled: pagination.total === 0,
431
+ children: formatMessage(getTrad("auditlog.export"))
284
432
  }
285
433
  ),
286
- Array.from({ length: pageCount }).map((_, i) => /* @__PURE__ */ jsx(
287
- PageLink,
288
- {
289
- number: i + 1,
290
- href: "#",
291
- onClick: (e) => {
292
- e.preventDefault();
293
- setPage(i + 1);
294
- },
295
- children: formatMessage(getTrad("pagination.page"), { page: i + 1 })
296
- },
297
- i + 1
298
- )),
299
434
  /* @__PURE__ */ jsx(
300
- NextLink,
435
+ ConfirmDialog,
301
436
  {
302
- href: "#",
303
- onClick: (e) => {
304
- e.preventDefault();
305
- setPage((p) => Math.min(pageCount, p + 1));
306
- },
307
- children: formatMessage(getTrad("pagination.next"))
437
+ trigger: /* @__PURE__ */ jsx(
438
+ Button,
439
+ {
440
+ size: "S",
441
+ variant: "danger-light",
442
+ startIcon: /* @__PURE__ */ jsx(Trash, {}),
443
+ disabled: pagination.total === 0,
444
+ children: formatMessage(getTrad("auditlog.clear"))
445
+ }
446
+ ),
447
+ title: formatMessage(getTrad("auditlog.clear.title")),
448
+ body: /* @__PURE__ */ jsx(Flex, { justifyContent: "center", children: /* @__PURE__ */ jsx(Typography, { textColor: "neutral800", textAlign: "center", children: formatMessage(getTrad("auditlog.clear.description"), {
449
+ count: pagination.total
450
+ }) }) }),
451
+ confirmLabel: formatMessage(getTrad("auditlog.clear")),
452
+ onConfirm: handleClearAll
308
453
  }
309
454
  )
310
- ] }) }) })
311
- ] })
455
+ ] })
456
+ ] }),
457
+ /* @__PURE__ */ jsxs(CustomTable, { colCount: 5, rowCount: records.length, children: [
458
+ /* @__PURE__ */ jsx(Thead, { children: /* @__PURE__ */ jsxs(Tr, { children: [
459
+ /* @__PURE__ */ jsx(Th, { children: formatMessage(getTrad("auditlog.table.timestamp")) }),
460
+ /* @__PURE__ */ jsx(Th, { children: formatMessage(getTrad("auditlog.table.action")) }),
461
+ /* @__PURE__ */ jsx(Th, { children: formatMessage(getTrad("auditlog.table.email")) }),
462
+ /* @__PURE__ */ jsx(Th, { children: formatMessage(getTrad("auditlog.table.ip")) }),
463
+ /* @__PURE__ */ jsx(Th, { children: formatMessage(getTrad("auditlog.table.details")) })
464
+ ] }) }),
465
+ /* @__PURE__ */ jsxs(Tbody, { children: [
466
+ loading && /* @__PURE__ */ jsx(Tr, { children: /* @__PURE__ */ jsx(Td, { colSpan: 5, children: /* @__PURE__ */ jsx(Flex, { justifyContent: "center", padding: 4, children: /* @__PURE__ */ jsx(Typography, { textColor: "neutral600", children: "Loading…" }) }) }) }),
467
+ !loading && records.length === 0 && /* @__PURE__ */ jsx(Tr, { children: /* @__PURE__ */ jsx(Td, { colSpan: 5, children: /* @__PURE__ */ jsx(Flex, { justifyContent: "center", padding: 4, children: /* @__PURE__ */ jsx(Typography, { textColor: "neutral600", children: formatMessage(getTrad("auditlog.table.empty")) }) }) }) }),
468
+ !loading && records.map((record) => /* @__PURE__ */ jsxs(Tr, { children: [
469
+ /* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsx(Typography, { variant: "omega", children: /* @__PURE__ */ jsx(LocalizedDate, { date: record.createdAt, options: { second: "2-digit" } }) }) }),
470
+ /* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "center", children: [
471
+ /* @__PURE__ */ jsx(Typography, { variant: "omega", children: record.action }),
472
+ /* @__PURE__ */ jsx(Tooltip, { label: formatMessage(getTrad(`auditlog.action.${record.action}`)), children: /* @__PURE__ */ jsx(
473
+ Information,
474
+ {
475
+ "aria-hidden": true,
476
+ style: { cursor: "help" },
477
+ width: "1.4rem",
478
+ height: "1.4rem",
479
+ fill: "primary600"
480
+ }
481
+ ) })
482
+ ] }) }),
483
+ /* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsx(Typography, { variant: "omega", children: record.email ?? "—" }) }),
484
+ /* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsx(Typography, { variant: "omega", children: record.ip ?? "—" }) }),
485
+ /* @__PURE__ */ jsx(Td, { style: { maxWidth: "200px" }, children: record.details ? /* @__PURE__ */ jsx(Tooltip, { label: record.details, side: "top", children: /* @__PURE__ */ jsx(Typography, { variant: "omega", textColor: "neutral600", style: DETAILS_TEXT_STYLE, children: record.details }) }) : /* @__PURE__ */ jsx(Typography, { variant: "omega", textColor: "neutral600", children: "—" }) })
486
+ ] }, record.id))
487
+ ] })
488
+ ] }),
489
+ /* @__PURE__ */ jsx(TablePagination, { page, pageCount: pagination.pageCount, onPageChange: setPage })
312
490
  ] });
313
491
  }
314
492
  const AlertMessage = styled.div`
@@ -345,23 +523,6 @@ function ErrorAlertMessage({ onClose }) {
345
523
  }
346
524
  ) });
347
525
  }
348
- function MatchedUserAlertMessage({
349
- onClose,
350
- count
351
- }) {
352
- const { formatMessage } = useIntl();
353
- const id = count > 1 ? "whitelist.users_exists" : "whitelist.user_exists";
354
- return /* @__PURE__ */ jsx(AlertMessage, { children: /* @__PURE__ */ jsx(
355
- Alert,
356
- {
357
- title: formatMessage(getTrad("alert.title.info")),
358
- variant: "default",
359
- closeLabel: "",
360
- onClose,
361
- children: formatMessage(getTrad(id))
362
- }
363
- ) });
364
- }
365
526
  const SwitchContainer = styled.label`
366
527
  position: relative;
367
528
  display: inline-block;
@@ -431,6 +592,15 @@ function CustomSwitch({ checked, onChange, label, disabled }) {
431
592
  label && /* @__PURE__ */ jsx(Typography, { variant: "pi", fontWeight: "bold", textColor: disabled ? "neutral500" : "neutral800", children: label })
432
593
  ] });
433
594
  }
595
+ function formatDatetimeForFilename(date) {
596
+ const year = date.getFullYear();
597
+ const month = String(date.getMonth() + 1).padStart(2, "0");
598
+ const day = String(date.getDate()).padStart(2, "0");
599
+ const hours = String(date.getHours()).padStart(2, "0");
600
+ const minutes = String(date.getMinutes()).padStart(2, "0");
601
+ const seconds = String(date.getSeconds()).padStart(2, "0");
602
+ return `${year}${month}${day}_${hours}${minutes}${seconds}`;
603
+ }
434
604
  function deepClone(value) {
435
605
  return JSON.parse(JSON.stringify(value));
436
606
  }
@@ -439,7 +609,6 @@ function useOidcSettings() {
439
609
  const [loading, setLoading] = useState(false);
440
610
  const [showSuccess, setSuccess] = useState(false);
441
611
  const [showError, setError] = useState(false);
442
- const [showMatched, setMatched] = useState(0);
443
612
  const [initialOidcRoles, setInitialOIDCRoles] = useState([]);
444
613
  const [oidcRoles, setOIDCRoles] = useState([]);
445
614
  const [roles, setRoles] = useState([]);
@@ -450,6 +619,7 @@ function useOidcSettings() {
450
619
  const [enforceOIDCConfig, setEnforceOIDCConfig] = useState(null);
451
620
  const [initialUsers, setInitialUsers] = useState([]);
452
621
  const [users, setUsers] = useState([]);
622
+ const [whitelistResponse, setWhitelistResponse] = useState({});
453
623
  useEffect(() => {
454
624
  get(`/strapi-plugin-oidc/oidc-roles`).then((response) => {
455
625
  setOIDCRoles(response.data);
@@ -459,6 +629,7 @@ function useOidcSettings() {
459
629
  setRoles(response.data.data);
460
630
  });
461
631
  get("/strapi-plugin-oidc/whitelist").then((response) => {
632
+ setWhitelistResponse(response.data);
462
633
  setUsers(response.data.whitelistUsers);
463
634
  setInitialUsers(deepClone(response.data.whitelistUsers));
464
635
  setUseWhitelist(response.data.useWhitelist);
@@ -474,8 +645,8 @@ function useOidcSettings() {
474
645
  );
475
646
  setOIDCRoles(updatedRoles);
476
647
  };
477
- const onRegisterWhitelist = (email, selectedRoles) => {
478
- const newUser = { email, roles: selectedRoles, createdAt: (/* @__PURE__ */ new Date()).toISOString() };
648
+ const onRegisterWhitelist = (email) => {
649
+ const newUser = { email, createdAt: (/* @__PURE__ */ new Date()).toISOString() };
479
650
  setUsers([...users, newUser]);
480
651
  };
481
652
  const onDeleteWhitelist = (email) => {
@@ -489,21 +660,19 @@ function useOidcSettings() {
489
660
  setUsers([]);
490
661
  if (useWhitelist) setEnforceOIDC(false);
491
662
  };
492
- const onImport = async (entries) => {
493
- const response = await post("/strapi-plugin-oidc/whitelist/import", { users: entries });
663
+ const onImport = async (emails) => {
664
+ const response = await post("/strapi-plugin-oidc/whitelist/import", {
665
+ users: emails.map((e) => ({ email: e }))
666
+ });
494
667
  const refreshed = await get("/strapi-plugin-oidc/whitelist");
495
668
  setUsers(refreshed.data.whitelistUsers);
496
669
  setInitialUsers(deepClone(refreshed.data.whitelistUsers));
497
670
  return response.data.importedCount;
498
671
  };
499
- const onExport = () => {
500
- const roleMap = new Map(roles.map((r) => [String(r.id), r.name]));
501
- const data = users.map(({ email, roles: userRoles }) => ({
502
- email,
503
- roles: (userRoles || []).map((id) => roleMap.get(String(id)) ?? id)
504
- }));
505
- const now = /* @__PURE__ */ new Date();
506
- const datetime = `${now.getFullYear()}${String(now.getMonth() + 1).padStart(2, "0")}${String(now.getDate()).padStart(2, "0")}_${String(now.getHours()).padStart(2, "0")}${String(now.getMinutes()).padStart(2, "0")}${String(now.getSeconds()).padStart(2, "0")}`;
672
+ const onExport = async () => {
673
+ const response = await get("/strapi-plugin-oidc/whitelist/export");
674
+ const data = response.data;
675
+ const datetime = formatDatetimeForFilename(/* @__PURE__ */ new Date());
507
676
  const blob = new Blob([JSON.stringify(data, null, 2)], { type: "application/json" });
508
677
  const url = URL.createObjectURL(blob);
509
678
  const a = document.createElement("a");
@@ -532,8 +701,8 @@ function useOidcSettings() {
532
701
  role: role.role
533
702
  }))
534
703
  });
535
- const syncResponse = await put("/strapi-plugin-oidc/whitelist/sync", {
536
- users: users.map((u) => ({ email: u.email, roles: u.roles }))
704
+ await put("/strapi-plugin-oidc/whitelist/sync", {
705
+ users: users.map((u) => ({ email: u.email }))
537
706
  });
538
707
  await put("/strapi-plugin-oidc/whitelist/settings", {
539
708
  useWhitelist,
@@ -543,16 +712,12 @@ function useOidcSettings() {
543
712
  setInitialUseWhitelist(useWhitelist);
544
713
  setInitialEnforceOIDC(enforceOIDC);
545
714
  get("/strapi-plugin-oidc/whitelist").then((getResponse) => {
715
+ setWhitelistResponse(getResponse.data);
546
716
  setUsers(getResponse.data.whitelistUsers);
547
717
  setInitialUsers(deepClone(getResponse.data.whitelistUsers));
548
718
  });
549
- if (syncResponse.data?.matchedExistingUsersCount > 0) {
550
- setMatched(syncResponse.data.matchedExistingUsersCount);
551
- setTimeout(() => setMatched(0), 3e3);
552
- } else {
553
- setSuccess(true);
554
- setTimeout(() => setSuccess(false), 3e3);
555
- }
719
+ setSuccess(true);
720
+ setTimeout(() => setSuccess(false), 3e3);
556
721
  } catch (e) {
557
722
  console.error(e);
558
723
  setError(true);
@@ -566,7 +731,6 @@ function useOidcSettings() {
566
731
  loading,
567
732
  showSuccess,
568
733
  showError,
569
- showMatched,
570
734
  oidcRoles,
571
735
  roles,
572
736
  useWhitelist,
@@ -574,12 +738,12 @@ function useOidcSettings() {
574
738
  enforceOIDCConfig,
575
739
  initialEnforceOIDC,
576
740
  users,
577
- isDirty
741
+ isDirty,
742
+ auditLogEnabled: whitelistResponse.auditLogEnabled ?? true
578
743
  },
579
744
  actions: {
580
745
  setSuccess,
581
746
  setError,
582
- setMatched,
583
747
  onChangeRole,
584
748
  onRegisterWhitelist,
585
749
  onDeleteWhitelist,
@@ -606,7 +770,6 @@ function HomePage() {
606
770
  ),
607
771
  state.showSuccess && /* @__PURE__ */ jsx(SuccessAlertMessage, { onClose: () => actions.setSuccess(false) }),
608
772
  state.showError && /* @__PURE__ */ jsx(ErrorAlertMessage, { onClose: () => actions.setError(false) }),
609
- state.showMatched > 0 && /* @__PURE__ */ jsx(MatchedUserAlertMessage, { count: state.showMatched, onClose: () => actions.setMatched(0) }),
610
773
  /* @__PURE__ */ jsx(Layouts.Content, { children: /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "stretch", gap: 6, children: [
611
774
  /* @__PURE__ */ jsxs(Box, { background: "neutral0", hasRadius: true, shadow: "filterShadow", padding: 6, children: [
612
775
  /* @__PURE__ */ jsx(Box, { paddingBottom: 4, children: /* @__PURE__ */ jsx(Typography, { variant: "beta", tag: "h2", children: formatMessage(getTrad("roles.title")) }) }),
@@ -636,8 +799,6 @@ function HomePage() {
636
799
  {
637
800
  loading: state.loading,
638
801
  users: state.users,
639
- roles: state.roles,
640
- oidcRoles: state.oidcRoles,
641
802
  useWhitelist: state.useWhitelist,
642
803
  onSave: actions.onRegisterWhitelist,
643
804
  onDelete: actions.onDeleteWhitelist,
@@ -681,7 +842,11 @@ function HomePage() {
681
842
  loading: state.loading,
682
843
  children: formatMessage(getTrad("page.save"))
683
844
  }
684
- ) })
845
+ ) }),
846
+ state.auditLogEnabled && /* @__PURE__ */ jsxs(Box, { background: "neutral0", hasRadius: true, shadow: "filterShadow", padding: 6, children: [
847
+ /* @__PURE__ */ jsx(Box, { paddingBottom: 4, children: /* @__PURE__ */ jsx(Typography, { variant: "beta", tag: "h2", children: formatMessage(getTrad("auditlog.title")) }) }),
848
+ /* @__PURE__ */ jsx(AuditLog, {})
849
+ ] })
685
850
  ] }) }),
686
851
  /* @__PURE__ */ jsx(Dialog.Root, { open: blocker.state === "blocked", children: /* @__PURE__ */ jsxs(Dialog.Content, { children: [
687
852
  /* @__PURE__ */ jsx(Dialog.Header, { children: formatMessage(getTrad("unsaved.title")) }),